Micron Document
NexusPi Git Node

Commit 5ed70dcca998f46476e219b1f303127546110242


Parents : e741d1c
Author : James L <jrl290@gmail.com>
Date : 2026-02-23T18:08:29-05:00

v1.0.0: Boundary mode with bidirectional LoRa↔TCP transport

Vendor microReticulum library with boundary mode transport fixes:
- Two-whitelist system gates backbone traffic (local addresses +
mentioned addresses from local devices)
- Allow control_hashes and local destinations through boundary filter
(fixes backbone→LoRa path discovery)
- Fix get_cached_packet() to call unpack() instead of update_hash()
(fixes empty destination_hash in path responses)
- LRPROOF Identity::recall null guard
- remaining_hops HEADER_1/BROADCAST fix for final-hop delivery
- PROOF packets excluded from boundary wrapping
- Iterator invalidation fix in transport table cleanup
- is_backbone flag replaces string matching for interface identification

Firmware changes:
- Set is_backbone(true) on backbone TCP interface
- Rename default TcpInterface name to BackboneInterface
- Update comments for dual-use TcpInterface (backbone + local AP)
- Use vendored lib/microReticulum instead of PlatformIO registry

Changes

66 files changed, 18264 insertions(+), 35 deletions(-)


Diff

diff --git a/BoundaryMode.h b/BoundaryMode.h
old mode 100644
new mode 100755
index d7c97c3..293af38
--- a/BoundaryMode.h
+++ b/BoundaryMode.h
@@ -20,7 +20,7 @@
// The boundary node operates with TWO RNS interfaces:
//
// 1. LoRaInterface (MODE_GATEWAY) — radio side, handles LoRa mesh
-// 2. TcpInterface (MODE_BOUNDARY) — WiFi side, connects to TCP backbone
+// 2. BackboneInterface (MODE_BOUNDARY) — WiFi side, connects to TCP backbone
//
// RNS Transport is ALWAYS enabled in boundary mode.
// Packets received on either interface are routed through Transport

diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino
index 878a52a..b931119 100755
--- a/RNode_Firmware.ino
+++ b/RNode_Firmware.ino
@@ -680,6 +680,7 @@ void setup() {
);
tcp_rns_interface = tcp_interface_ptr;
tcp_rns_interface.mode(RNS::Type::Interface::MODE_BOUNDARY);
+ tcp_rns_interface.is_backbone(true);
RNS::Transport::register_interface(tcp_rns_interface);
{

diff --git a/TcpInterface.h b/TcpInterface.h
old mode 100644
new mode 100755
index f61de3b..30d88d5
--- a/TcpInterface.h
+++ b/TcpInterface.h
@@ -1,9 +1,9 @@
// Copyright (C) 2026, Boundary Mode Extension
// Based on microReticulum_Firmware by Mark Qvist
//
-// TcpInterface — An RNS InterfaceImpl that bridges the WiFi TCP
-// connection as a second RNS transport interface, enabling
-// Boundary mode operation between LoRa and TCP/IP backbone.
+// TcpInterface — An RNS InterfaceImpl that bridges a WiFi TCP
+// connection as an RNS transport interface. Used for both the
+// backbone (BackboneInterface) and local AP (LocalTcpInterface).
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -61,7 +61,7 @@ class TcpInterface : public RNS::InterfaceImpl {
public:
TcpInterface(TcpIfMode mode, uint16_t port = TCP_IF_DEFAULT_PORT,
const char* target_host = nullptr, uint16_t target_port = 0,
- const char* name = "TcpInterface")
+ const char* name = "BackboneInterface")
: RNS::InterfaceImpl(name),
_mode(mode),
_port(port),

diff --git a/flash.py b/flash.py
old mode 100644
new mode 100755

diff --git a/lib/microReticulum/LICENSE b/lib/microReticulum/LICENSE
new file mode 100755
index 0000000..261eeb9
--- /dev/null
+++ b/lib/microReticulum/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.

diff --git a/lib/microReticulum/library.json b/lib/microReticulum/library.json
new file mode 100755
index 0000000..b7fb833
--- /dev/null
+++ b/lib/microReticulum/library.json
@@ -0,0 +1,31 @@
+{
+ "name": "microReticulum",
+ "version": "0.2.4",
+ "description": "Port of Reticulum Network Stack to C++ specifically but not exclusively targeting 32-bit and better MCUs",
+ "keywords": "reticulum, rns, mesh, embedded, mcu, esp32, nrf52, native",
+ "repository":
+ {
+ "type": "git",
+ "url": "https://github.com/attermann/microReticulum.git"
+ },
+ "authors":
+ [
+ {
+ "name": "Chad Attermann",
+ "email": "attermann@gmail.com",
+ "maintainer": true
+ }
+ ],
+ "license": "Apache-2.0",
+ "homepage": "https://reticulum.network/",
+ "dependencies": {
+ "ArduinoJson": "~7.4.2",
+ "MsgPack": "~0.4.2"
+ },
+ "frameworks": "*",
+ "platforms": "*",
+ "export": {},
+ "exclude": [
+ ".github"
+ ]
+}
\ No newline at end of file

diff --git a/lib/microReticulum/library.properties b/lib/microReticulum/library.properties
new file mode 100644
index 0000000..ace0975
--- /dev/null
+++ b/lib/microReticulum/library.properties
@@ -0,0 +1,10 @@
+name=microReticulum
+version=0.2.4
+author=Chad Attermann <attermann@gmail.com>
+maintainer=Chad Attermann <attermann@gmail.com>
+sentence=Port of Reticulum Network Stack to C++
+paragraph=Specifically but not exclusively targeting 32-bit and better MCUs
+category=Communication
+url=https://github.com/attermann/microReticulum.git
+architectures=*
+depends=ArduinoJson,MsgPack

diff --git a/lib/microReticulum/src/Bytes.cpp b/lib/microReticulum/src/Bytes.cpp
new file mode 100755
index 0000000..0d1e001
--- /dev/null
+++ b/lib/microReticulum/src/Bytes.cpp
@@ -0,0 +1,201 @@
+#include "Bytes.h"
+
+using namespace RNS;
+
+// Creates new shared data for instance
+// - If capacity is specified (>0) then create empty shared data with initial reserved capacity
+// - If capacity is not specified (<=0) then create empty shared data with no initial capacity
+void Bytes::newData(size_t capacity /*= 0*/) {
+//MEMF("Bytes is creating own data with capacity %u", capacity);
+//MEM("newData: Creating new data...");
+ Data* data = new Data();
+ if (data == nullptr) {
+ ERROR("Bytes failed to allocate empty data buffer");
+ throw std::runtime_error("Failed to allocate empty data buffer");
+ }
+//MEM("newData: Created new data");
+ if (capacity > 0) {
+//MEMF("newData: Reserving data capacity of %u...", capacity);
+ data->reserve(capacity);
+//MEM("newData: Reserved data capacity");
+ }
+//MEM("newData: Assigning data to shared data pointer...");
+ _data = SharedData(data);
+//MEM("newData: Assigned data to shared data pointer");
+ _exclusive = true;
+}
+
+// Ensures that instance has exclusive shared data
+// - If instance has no shared data then create new shared data
+// - If instance does not have exclusive on shared data that is not empty then make a copy of shared data (if requests) and reserve capacity (if requested)
+// - If instance does not have exclusive on shared data that is empty then create new shared data
+// - If instance already has exclusive on shared data then do nothing except reserve capacity (if requested)
+void Bytes::exclusiveData(bool copy /*= true*/, size_t capacity /*= 0*/) {
+ if (!_data) {
+ newData(capacity);
+ }
+ else if (!_exclusive) {
+ if (copy && !_data->empty()) {
+ //TRACE("Bytes is creating a writable copy of its shared data");
+ //Data* data = new Data(*_data.get());
+//MEM("exclusiveData: Creating new data...");
+ Data* data = new Data();
+ if (data == nullptr) {
+ ERROR("Bytes failed to duplicate data buffer");
+ throw std::runtime_error("Failed to duplicate data buffer");
+ }
+//MEM("exclusiveData: Created new data");
+ if (capacity > 0) {
+//MEMF("exclusiveData: Reserving data capacity of %u...", capacity);
+ // if requested capacity < existing size then reserve capacity for existing size instead
+ data->reserve((capacity > _data->size()) ? capacity : _data->size());
+//MEM("exclusiveData: Reserved data capacity");
+ }
+ else {
+ data->reserve(_data->size());
+ }
+//MEM("exclusiveData: Copying existing data...");
+ data->insert(data->begin(), _data->begin(), _data->end());
+//MEM("exclusiveData: Copied existing data");
+//MEM("exclusiveData: Assigning data to shared data pointer...");
+ _data = SharedData(data);
+//MEM("exclusiveData: Assigned data to shared data pointer");
+ _exclusive = true;
+ }
+ else {
+//MEM("Bytes is creating its own data because shared is empty");
+ //data = new Data();
+ //if (data == nullptr) {
+ // ERROR("Bytes failed to allocate empty data buffer");
+ // throw std::runtime_error("Failed to allocate empty data buffer");
+ //}
+ //_data = SharedData(data);
+ //_exclusive = true;
+//MEM("exclusiveData: Creating new empty data...");
+ newData(capacity);
+//MEM("exclusiveData: Created new empty data");
+ }
+ }
+ else if (capacity > 0 && capacity > size()) {
+ reserve(capacity);
+ }
+}
+
+int Bytes::compare(const Bytes& bytes) const {
+ if (_data == bytes._data) {
+ return 0;
+ }
+ else if (!_data) {
+ return -1;
+ }
+ else if (!bytes._data) {
+ return 1;
+ }
+ else if (*_data < *(bytes._data)) {
+ return -1;
+ }
+ else if (*_data > *(bytes._data)) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+int Bytes::compare(const uint8_t* buf, size_t size) const {
+ if (!_data && size == 0) {
+ return 0;
+ }
+ else if (!_data) {
+ return -1;
+ }
+ int cmp = memcmp(_data->data(), buf, (_data->size() < size) ? _data->size() : size);
+ if (cmp == 0 && _data->size() < size) {
+ return -1;
+ }
+ else if (cmp == 0 && _data->size() > size) {
+ return 1;
+ }
+ return cmp;
+}
+
+void Bytes::assignHex(const uint8_t* hex, size_t hex_size) {
+ // if assignment is empty then clear data and don't bother creating new
+ if (hex == nullptr || hex_size <= 0) {
+ _data = nullptr;
+ _exclusive = true;
+ return;
+ }
+ exclusiveData(false, hex_size / 2);
+ // need to clear data since we're appending below
+ _data->clear();
+ for (size_t i = 0; i < hex_size; i += 2) {
+ uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
+ _data->push_back(byte);
+ }
+}
+
+void Bytes::appendHex(const uint8_t* hex, size_t hex_size) {
+ // if append is empty then do nothing
+ if (hex == nullptr || hex_size <= 0) {
+ return;
+ }
+ exclusiveData(true, size() + (hex_size / 2));
+ for (size_t i = 0; i < hex_size; i += 2) {
+ uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
+ _data->push_back(byte);
+ }
+}
+
+const char hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+const char hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+std::string RNS::hexFromByte(uint8_t byte, bool upper /*= true*/) {
+ std::string hex;
+ if (upper) {
+ hex += hex_upper_chars[ (byte& 0xF0) >> 4];
+ hex += hex_upper_chars[ (byte& 0x0F) >> 0];
+ }
+ else {
+ hex += hex_lower_chars[ (byte& 0xF0) >> 4];
+ hex += hex_lower_chars[ (byte& 0x0F) >> 0];
+ }
+ return hex;
+}
+
+std::string Bytes::toHex(bool upper /*= false*/) const {
+ if (!_data) {
+ return "";
+ }
+ std::string hex;
+ for (uint8_t byte : *_data) {
+ if (upper) {
+ hex += hex_upper_chars[ (byte& 0xF0) >> 4];
+ hex += hex_upper_chars[ (byte& 0x0F) >> 0];
+ }
+ else {
+ hex += hex_lower_chars[ (byte& 0xF0) >> 4];
+ hex += hex_lower_chars[ (byte& 0x0F) >> 0];
+ }
+ }
+ return hex;
+}
+
+// mid
+Bytes Bytes::mid(size_t beginpos, size_t len) const {
+ if (!_data || beginpos >= size()) {
+ return NONE;
+ }
+ if ((beginpos + len) >= size()) {
+ len = (size() - beginpos);
+ }
+ return {data() + beginpos, len};
+}
+
+// to end
+Bytes Bytes::mid(size_t beginpos) const {
+ if (!_data || beginpos >= size()) {
+ return NONE;
+ }
+ return {data() + beginpos, size() - beginpos};
+}

diff --git a/lib/microReticulum/src/Bytes.h b/lib/microReticulum/src/Bytes.h
new file mode 100755
index 0000000..120e1e3
--- /dev/null
+++ b/lib/microReticulum/src/Bytes.h
@@ -0,0 +1,447 @@
+#pragma once
+
+#include "Log.h"
+
+#include <ArduinoJson.h>
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <vector>
+#include <string>
+#include <memory>
+
+// Finds the start of the first occurrence of the substring needle of length needlelen in the memory area haystack of length haystacklen.
+inline void* memmem(const void* haystack, size_t haystack_len, const void* needle, size_t needle_len) {
+ const unsigned char* h = (const unsigned char*)haystack;
+ const unsigned char* n = (const unsigned char*)needle;
+
+ if (needle_len == 0)
+ return (void*)h;
+
+ if (haystack_len < needle_len)
+ return nullptr;
+
+ for (size_t i = 0; i <= haystack_len - needle_len; i++) {
+ if (h[i] == n[0] && memcmp(&h[i], n, needle_len) == 0) {
+ return (void*)&h[i];
+ }
+ }
+
+ return nullptr;
+}
+
+namespace RNS {
+
+ #define COW
+
+ class Bytes {
+
+ private:
+ //typedef std::vector<uint8_t> Data;
+ using Data = std::vector<uint8_t>;
+ //typedef std::shared_ptr<Data> SharedData;
+ using SharedData = std::shared_ptr<Data>;
+
+ public:
+ enum NoneConstructor {
+ NONE
+ };
+
+ public:
+ Bytes() {
+ MEMF("Bytes object created from default, this: %lu, data: %lu", this, _data.get());
+ }
+ Bytes(const NoneConstructor none) {
+ MEMF("Bytes object created from NONE, this: %lu, data: %lu", this, _data.get());
+ }
+ Bytes(const Bytes& bytes) {
+//MEM("Bytes is using shared data");
+ assign(bytes);
+ MEMF("Bytes object copy created from bytes \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+ // Construct from std::vector<uint8_t>
+ Bytes(const Data& data) {
+MEM("Creating from data-copy...");
+ assign(data);
+ MEMF("Bytes object created from data-copy \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+ // Construct from rvalue std::vector<uint8_t> (move)
+ Bytes(Data&& rdata) {
+MEM("Creating from data-move...");
+ assign(std::move(rdata));
+ MEMF("Bytes object created from data-move \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+ Bytes(const uint8_t* chunk, size_t size) {
+ assign(chunk, size);
+ MEMF("Bytes object created from chunk \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+ Bytes(const void* chunk, size_t size) {
+ assign(chunk, size);
+ MEMF("Bytes object created from chunk \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+ Bytes(const char* string) {
+ assign(string);
+ MEMF("Bytes object created from string \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+ Bytes(const std::string& string) {
+ assign(string);
+ MEMF("Bytes object created from std::string \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+ Bytes(size_t capacity) {
+ newData(capacity);
+ MEMF("Bytes object created with capacity %u, this: %lu, data: %lu", capacity, this, _data.get());
+ }
+ virtual ~Bytes() {
+ MEMF("Bytes object destroyed \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
+ }
+
+ inline const Bytes& operator = (const Bytes& bytes) {
+ assign(bytes);
+ return *this;
+ }
+ inline const Bytes& operator += (const Bytes& bytes) {
+ append(bytes);
+ return *this;
+ }
+
+ // CBA TODO Resolve ambiguity in JsonVariantConst assignments before enabling the following
+/*
+ // Assignment from std::vector<uint8_t>
+ inline const Bytes& operator = (const Data& data) {
+ assign(data);
+ return *this;
+ }
+ // Move assignment from std::vector<uint8_t>
+ inline const Bytes& operator = (Data&& rdata) {
+ assign(std::move(rdata));
+ return *this;
+ }
+*/
+ inline const Bytes& operator += (const Data& data) {
+ append(data);
+ return *this;
+ }
+
+ inline Bytes operator + (const Bytes& bytes) const {
+ Bytes newbytes(*this);
+ newbytes.append(bytes);
+ return newbytes;
+ }
+ inline bool operator == (const Bytes& bytes) const {
+ return compare(bytes) == 0;
+ }
+ inline bool operator != (const Bytes& bytes) const {
+ return compare(bytes) != 0;
+ }
+ inline bool operator < (const Bytes& bytes) const {
+ return compare(bytes) < 0;
+ }
+ inline bool operator > (const Bytes& bytes) const {
+ return compare(bytes) > 0;
+ }
+
+ inline uint8_t& operator[](size_t index) {
+ if (!_data || index >= _data->size()) {
+ throw std::out_of_range("Index out of bounds");
+ }
+ return (*_data)[index];
+ }
+
+ inline const uint8_t& operator[](size_t index) const {
+ if (!_data || index >= _data->size()) {
+ throw std::out_of_range("Index out of bounds");
+ }
+ return (*_data)[index];
+ }
+
+ inline operator bool() const {
+ return (_data && !_data->empty());
+ }
+ inline operator const Data() const {
+ if (!_data)
+ return Data();
+ return *_data.get();
+ }
+ // CBA NOTE: Following cast operators can cause issues with ambiguity from other libraries
+/*
+ inline operator const uint8_t*() const {
+ if (!_data)
+ return nullptr;
+ return _data->data();
+ }
+ inline operator const void*() const {
+ if (!_data)
+ return nullptr;
+ return _data->data();
+ }
+*/
+
+ private:
+ inline SharedData shareData() const {
+//MEM("Bytes is sharing its own data");
+ _exclusive = false;
+ return _data;
+ }
+ void newData(size_t capacity = 0);
+ void exclusiveData(bool copy = true, size_t capacity = 0);
+
+ public:
+ inline void clear() {
+ _data = nullptr;
+ _exclusive = true;
+ }
+
+ inline void assign(const Bytes& bytes) {
+#ifdef COW
+ _data = bytes.shareData();
+ _exclusive = false;
+#else
+ // if assignment is empty then clear data and don't bother creating new
+ if (bytes.size() <= 0) {
+ _data = nullptr;
+ _exclusive = true;
+ return;
+ }
+ exclusiveData(false, bytes._data->size());
+ //_data->insert(_data->begin(), bytes._data->begin(), bytes._data->end());
+ //_data->assign(bytes._data->begin(), bytes._data->end());
+ *_data = bytes._data;
+#endif
+ }
+ inline void assign(const Data& data) {
+ // if assignment is empty then clear data and don't bother creating new
+ if (data.size() <= 0) {
+ _data = nullptr;
+ _exclusive = true;
+ return;
+ }
+ exclusiveData(false, data.size());
+ //_data->assign(data.begin(), data.end());
+ *_data = data;
+ }
+ inline void assign(Data&& rdata) {
+ // if assignment is empty then clear data and don't bother creating new
+ if (rdata.size() <= 0) {
+ _data = nullptr;
+ _exclusive = true;
+ return;
+ }
+ exclusiveData(false);
+ *_data = std::move(rdata);
+ }
+ inline void assign(const uint8_t* chunk, size_t chunk_size) {
+ // if assignment is empty then clear data and don't bother creating new
+ if (chunk == nullptr || chunk_size <= 0) {
+ _data = nullptr;
+ _exclusive = true;
+ return;
+ }
+ exclusiveData(false, chunk_size);
+ //_data->insert(_data->begin(), chunk, chunk + chunk_size);
+ _data->assign(chunk, chunk + chunk_size);
+ }
+ inline void assign(const void* chunk, size_t chunk_size) {
+ assign((uint8_t*)chunk, chunk_size);
+ }
+ inline void assign(const char* string) {
+ // if assignment is empty then clear data and don't bother creating new
+ if (string == nullptr || string[0] == 0) {
+ _data = nullptr;
+ _exclusive = true;
+ return;
+ }
+ size_t string_size = strlen(string);
+ exclusiveData(false, string_size);
+ //_data->insert(_data->begin(), (uint8_t* )string, (uint8_t* )string + string_size);
+ _data->assign((uint8_t* )string, (uint8_t* )string + string_size);
+ }
+ //inline void assign(const std::string& string) { assign(string.c_str()); }
+ inline void assign(const std::string& string) { assign((uint8_t*)string.c_str(), string.length()); }
+ void assignHex(const uint8_t* hex, size_t hex_size);
+ inline void assignHex(const char* hex) { assignHex((uint8_t*)hex, strlen(hex)); }
+
+ inline void append(const Bytes& bytes) {
+ // if append is empty then do nothing
+ if (bytes.size() <= 0) {
+ return;
+ }
+ exclusiveData(true, size() + bytes.size());
+ _data->insert(_data->end(), bytes._data->begin(), bytes._data->end());
+ }
+ inline void append(const Data& data) {
+ // if append is empty then do nothing
+ if (data.size() <= 0) {
+ return;
+ }
+ exclusiveData(true, size() + data.size());
+ _data->insert(_data->end(), data.begin(), data.end());
+ }
+ inline void append(const uint8_t* chunk, size_t chunk_size) {
+ // if append is empty then do nothing
+ if (chunk == nullptr || chunk_size <= 0) {
+ return;
+ }
+ exclusiveData(true, size() + chunk_size);
+ _data->insert(_data->end(), chunk, chunk + chunk_size);
+ }
+ inline void append(const void* chunk, size_t chunk_size) {
+ append((uint8_t*)chunk, chunk_size);
+ }
+ inline void append(const char* string) {
+ // if append is empty then do nothing
+ if (string == nullptr || string[0] == 0) {
+ return;
+ }
+ size_t string_size = strlen(string);
+ exclusiveData(true, size() + string_size);
+ _data->insert(_data->end(), (uint8_t* )string, (uint8_t* )string + string_size);
+ }
+ inline void append(uint8_t byte) {
+ exclusiveData(true, size() + 1);
+ _data->push_back(byte);
+ }
+ //inline void append(const std::string& string) { append(string.c_str()); }
+ inline void append(const std::string& string) { append((uint8_t*)string.c_str(), string.length()); }
+ void appendHex(const uint8_t* hex, size_t hex_size);
+ inline void appendHex(const char* hex) { appendHex((uint8_t*)hex, strlen(hex)); }
+
+ inline uint8_t* writable(size_t size) {
+ if (size > 0) {
+ // create exclusive data with reserved capacity
+ exclusiveData(false, size);
+ // actually expand data to requested size
+ resize(size);
+ return _data->data();
+ }
+ else if (_data) {
+ size_t current_size = _data->size();
+ // create exclusive data with reserved capacity
+ exclusiveData(false, current_size);
+ // actually expand data to current size
+ resize(current_size);
+ return _data->data();
+ }
+ return nullptr;
+ }
+
+ inline void resize(size_t newsize) {
+ // if size is unchanged then do nothing
+ if (newsize == size()) {
+ return;
+ }
+ // CBA TODO Determine whether or not to reserve capacity here since when size is shrunk the call to
+ // exclusive data may copy all data firt which will effectively grow the data again before being
+ // shrunk below (inefficient).
+ exclusiveData(true);
+ _data->resize(newsize);
+ }
+
+ public:
+ int compare(const Bytes& bytes) const;
+ int compare(const uint8_t* buf, size_t size) const;
+ inline int compare(const char* str) const { return compare((const uint8_t*)str, strlen(str)); }
+ inline size_t size() const { if (!_data) return 0; return _data->size(); }
+ inline bool empty() const { if (!_data) return true; return _data->empty(); }
+ inline size_t capacity() const { if (!_data) return 0; return _data->capacity(); }
+ inline void reserve(size_t capacity) const { if (!_data) return; _data->reserve(capacity); }
+ inline const uint8_t* data() const { if (!_data) return nullptr; return _data->data(); }
+ inline const Data collection() const { if (!_data) return Data(); return *_data.get(); }
+
+ inline std::string toString() const { if (!_data) return ""; return {(const char*)data(), size()}; }
+ std::string toHex(bool upper = false) const;
+ Bytes mid(size_t beginpos, size_t len) const;
+ Bytes mid(size_t beginpos) const;
+ inline Bytes left(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data(), len}; }
+ inline Bytes right(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data() + (size() - len), len}; }
+ inline int find(int pos, const char* str) {
+ if (!_data || _data->data() == nullptr || (size_t)pos >= _data->size()) {
+ return -1;
+ }
+ //const char* ptr = strnstr((const char*)(_data->data() + pos), str, (_data->size() - pos));
+ void* ptr = memmem((const void*)(_data->data() + pos), (_data->size() - pos), (const void*)str, strlen(str));
+ if (ptr == nullptr) {
+ return -1;
+ }
+ return (int)((uint8_t*)ptr - _data->data());
+ }
+ inline int find(const char* str) { return find(0, str); }
+
+ // Python array indexing
+ // [8:16]
+ // pos 8 to pos 16
+ // mid(8, 8)
+ // [:16]
+ // start to pos 16 (same as first 16)
+ // left(16)
+ // [16:]
+ // pos 16 to end
+ // mid(16)
+ // [-16:]
+ // last 16
+ // right(16)
+ // [:-16]
+ // all except the last 16
+ // left(size()-16)
+ // mid(0, size()-16)
+ // [-1]
+ // last element
+ // [-2]
+ // second to last element
+
+ private:
+ SharedData _data;
+ mutable bool _exclusive = true;
+
+ };
+
+ // following array function doesn't work without size since it's passed as a pointer to the array so sizeof() is of the pointer
+ //inline Bytes bytesFromArray(const uint8_t arr[]) { return Bytes(arr, sizeof(arr)); }
+ //inline Bytes bytesFromChunk(const uint8_t* ptr, size_t len) { return Bytes(ptr, len); }
+ inline Bytes bytesFromChunk(const uint8_t* ptr, size_t len) { return {ptr, len}; }
+ //inline Bytes bytesFromString(const char* str) { return Bytes((uint8_t*)str, strlen(str)); }
+ inline Bytes bytesFromString(const char* str) { return {(uint8_t*)str, strlen(str)}; }
+ //z inline Bytes bytesFromInt(const int) { return {(uint8_t*)str, strlen(str)}; }
+
+ inline std::string stringFromBytes(const Bytes& bytes) { return bytes.toString(); }
+ inline std::string hexFromBytes(const Bytes& bytes) { return bytes.toHex(); }
+ std::string hexFromByte(uint8_t byte, bool upper = true);
+
+}
+
+inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, const RNS::Bytes& rhbytes) {
+//MEM("Appending right-hand Bytes to left-hand Bytes");
+ lhbytes.append(rhbytes);
+//MEM("Returning left-hand Bytes");
+ return lhbytes;
+}
+
+inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, uint8_t rhbyte) {
+//MEM("Appending right-hand byte to left-hand Bytes");
+ lhbytes.append(rhbyte);
+//MEM("Returning left-hand Bytes");
+ return lhbytes;
+}
+
+inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, const char* rhstr) {
+//MEM("Appending right-hand str to left-hand Bytes");
+ lhbytes.append(rhstr);
+//MEM("Returning left-hand Bytes");
+ return lhbytes;
+}
+
+namespace ArduinoJson {
+ // Serialize
+ inline bool convertToJson(const RNS::Bytes& src, JsonVariant dst) {
+ return dst.set(src.toHex());
+ }
+ // Deserialize
+ inline void convertFromJson(JsonVariantConst src, RNS::Bytes& dst) {
+ dst.assignHex(src.as<const char*>());
+ }
+ inline bool canConvertFromJson(JsonVariantConst src, const RNS::Bytes&) {
+ return src.is<const char*>();
+ }
+}

diff --git a/lib/microReticulum/src/Channel.cpp b/lib/microReticulum/src/Channel.cpp
new file mode 100755
index 0000000..ba6e66c
--- /dev/null
+++ b/lib/microReticulum/src/Channel.cpp
@@ -0,0 +1,12 @@
+#include "Channel.h"
+
+#include "Reticulum.h"
+#include "Transport.h"
+#include "Packet.h"
+#include "Log.h"
+
+#include <algorithm>
+
+using namespace RNS;
+using namespace RNS::Type::Channel;
+using namespace RNS::Utilities;

diff --git a/lib/microReticulum/src/Channel.h b/lib/microReticulum/src/Channel.h
new file mode 100755
index 0000000..4425be7
--- /dev/null
+++ b/lib/microReticulum/src/Channel.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "Destination.h"
+#include "Type.h"
+
+#include <memory>
+#include <cassert>
+
+namespace RNS {
+
+ class Channel {
+
+ public:
+ Channel(Type::NoneConstructor none) {
+ MEM("Channel NONE object created");
+ }
+ Channel(const Channel& resource) : _object(resource._object) {
+ MEM("Channel object copy created");
+ }
+ virtual ~Channel(){
+ MEM("Channel object destroyed");
+ }
+
+ Channel& operator = (const Channel& resource) {
+ _object = resource._object;
+ return *this;
+ }
+ operator bool() const {
+ return _object.get() != nullptr;
+ }
+ bool operator < (const Channel& resource) const {
+ return _object.get() < resource._object.get();
+ //return _object->_hash < resource._object->_hash;
+ }
+
+ public:
+ void _shutdown() {}
+
+ private:
+ class Object {
+ public:
+ Object() { MEM("Channel::Data object created, this: " + std::to_string((uintptr_t)this)); }
+ virtual ~Object() { MEM("Channel::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
+ private:
+
+ friend class Channel;
+ };
+ std::shared_ptr<Object> _object;
+
+ };
+
+
+ class ChannelAdvertisement {
+
+ };
+
+}

diff --git a/lib/microReticulum/src/Cryptography/AES.h b/lib/microReticulum/src/Cryptography/AES.h
new file mode 100755
index 0000000..d5927e5
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/AES.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#include "CBC.h"
+
+#include "../Bytes.h"
+
+#include <AES.h>
+
+namespace RNS { namespace Cryptography {
+
+ class AES_128_CBC {
+
+ public:
+ static inline const Bytes encrypt(const Bytes& plaintext, const Bytes& key, const Bytes& iv) {
+ CBC<AES128> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ Bytes ciphertext;
+ cbc.encrypt(ciphertext.writable(plaintext.size()), plaintext.data(), plaintext.size());
+ return ciphertext;
+ }
+
+ static inline const Bytes decrypt(const Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
+ CBC<AES128> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ Bytes plaintext;
+ cbc.decrypt(plaintext.writable(ciphertext.size()), ciphertext.data(), ciphertext.size());
+ return plaintext;
+ }
+
+ // EXPERIMENTAL - overwrites passed buffer
+ static inline void inplace_encrypt(Bytes& plaintext, const Bytes& key, const Bytes& iv) {
+ CBC<AES128> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ cbc.encrypt((uint8_t*)plaintext.data(), plaintext.data(), plaintext.size());
+ }
+
+ // EXPERIMENTAL - overwrites passed buffer
+ static inline void inplace_decrypt(Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
+ CBC<AES128> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size());
+ }
+
+ };
+
+ class AES_256_CBC {
+
+ public:
+ static inline const Bytes encrypt(const Bytes& plaintext, const Bytes& key, const Bytes& iv) {
+ CBC<AES256> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ Bytes ciphertext;
+ cbc.encrypt(ciphertext.writable(plaintext.size()), plaintext.data(), plaintext.size());
+ return ciphertext;
+ }
+
+ static inline const Bytes decrypt(const Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
+ CBC<AES256> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ Bytes plaintext;
+ cbc.decrypt(plaintext.writable(ciphertext.size()), ciphertext.data(), ciphertext.size());
+ return plaintext;
+ }
+
+ // EXPERIMENTAL - overwrites passed buffer
+ static inline void inplace_encrypt(Bytes& plaintext, const Bytes& key, const Bytes& iv) {
+ CBC<AES256> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ cbc.encrypt((uint8_t*)plaintext.data(), plaintext.data(), plaintext.size());
+ }
+
+ // EXPERIMENTAL - overwrites passed buffer
+ static inline void inplace_decrypt(Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
+ CBC<AES256> cbc;
+ cbc.setKey(key.data(), key.size());
+ cbc.setIV(iv.data(), iv.size());
+ cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size());
+ }
+
+ };
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/CBC.cpp b/lib/microReticulum/src/Cryptography/CBC.cpp
new file mode 100755
index 0000000..b18e5ce
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/CBC.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "CBC.h"
+#include <Crypto.h>
+#include <string.h>
+
+/**
+ * \class CBCCommon CBC.h <CBC.h>
+ * \brief Concrete base class to assist with implementing CBC for
+ * 128-bit block ciphers.
+ *
+ * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+ *
+ * \sa CBC
+ */
+
+/**
+ * \brief Constructs a new cipher in CBC mode.
+ *
+ * This constructor should be followed by a call to setBlockCipher().
+ */
+CBCCommon::CBCCommon()
+ : blockCipher(0)
+ , posn(16)
+{
+}
+
+/**
+ * \brief Destroys this cipher object after clearing sensitive information.
+ */
+CBCCommon::~CBCCommon()
+{
+ clean(iv);
+ clean(temp);
+}
+
+size_t CBCCommon::keySize() const
+{
+ return blockCipher->keySize();
+}
+
+size_t CBCCommon::ivSize() const
+{
+ return 16;
+}
+
+bool CBCCommon::setKey(const uint8_t* key, size_t len)
+{
+ // Verify the cipher's block size, just in case.
+ if (blockCipher->blockSize() != 16)
+ return false;
+
+ // Set the key on the underlying block cipher.
+ return blockCipher->setKey(key, len);
+}
+
+bool CBCCommon::setIV(const uint8_t* iv, size_t len)
+{
+ if (len != 16)
+ return false;
+ memcpy(this->iv, iv, 16);
+ posn = 16;
+ return true;
+}
+
+void CBCCommon::encrypt(uint8_t* output, const uint8_t* input, size_t len)
+{
+ uint8_t posn;
+ while (len >= 16) {
+ for (posn = 0; posn < 16; ++posn)
+ iv[posn] ^= *input++;
+ blockCipher->encryptBlock(iv, iv);
+ for (posn = 0; posn < 16; ++posn)
+ *output++ = iv[posn];
+ len -= 16;
+ }
+}
+
+void CBCCommon::decrypt(uint8_t* output, const uint8_t* input, size_t len)
+{
+ uint8_t posn;
+ while (len >= 16) {
+ blockCipher->decryptBlock(temp, input);
+ for (posn = 0; posn < 16; ++posn) {
+ uint8_t in = *input++;
+ *output++ = temp[posn] ^ iv[posn];
+ iv[posn] = in;
+ }
+ len -= 16;
+ }
+}
+
+void CBCCommon::clear()
+{
+ blockCipher->clear();
+ clean(iv);
+ clean(temp);
+ posn = 16;
+}
+
+/**
+ * \fn void CBCCommon::setBlockCipher(BlockCipher *cipher)
+ * \brief Sets the block cipher to use for this CBC object.
+ *
+ * \param cipher The block cipher to use to implement CBC mode,
+ * which must have a block size of 16 bytes (128 bits).
+ */
+
+/**
+ * \class CBC CBC.h <CBC.h>
+ * \brief Implementation of the Cipher Block Chaining (CBC) mode for
+ * 128-bit block ciphers.
+ *
+ * The template parameter T must be a concrete subclass of BlockCipher
+ * indicating the specific block cipher to use. T must have a block size
+ * of 16 bytes (128 bits).
+ *
+ * For example, the following creates a CBC object using AES192 as the
+ * underlying cipher:
+ *
+ * \code
+ * CBC<AES192> cbc;
+ * cbc.setKey(key, 24);
+ * cbc.setIV(iv, 16);
+ * cbc.encrypt(output, input, len);
+ * \endcode
+ *
+ * Decryption is similar:
+ *
+ * \code
+ * CBC<AES192> cbc;
+ * cbc.setKey(key, 24);
+ * cbc.setIV(iv, 16);
+ * cbc.decrypt(output, input, len);
+ * \endcode
+ *
+ * The size of the ciphertext will always be the same as the size of
+ * the plaintext. Also, the length of the plaintext/ciphertext must be a
+ * multiple of 16. Extra bytes are ignored and not encrypted. The caller
+ * is responsible for padding the underlying data to a multiple of 16
+ * using an appropriate padding scheme for the application.
+ *
+ * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+ *
+ * \sa CTR, CFB, OFB
+ */
+
+/**
+ * \fn CBC::CBC()
+ * \brief Constructs a new CBC object for the block cipher T.
+ */
\ No newline at end of file

diff --git a/lib/microReticulum/src/Cryptography/CBC.h b/lib/microReticulum/src/Cryptography/CBC.h
new file mode 100755
index 0000000..8bb476d
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/CBC.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_CBC_h
+#define CRYPTO_CBC_h
+
+#include <Cipher.h>
+#include <BlockCipher.h>
+
+class CBCCommon : public Cipher
+{
+public:
+ virtual ~CBCCommon();
+
+ size_t keySize() const;
+ size_t ivSize() const;
+
+ bool setKey(const uint8_t* key, size_t len);
+ bool setIV(const uint8_t* iv, size_t len);
+
+ void encrypt(uint8_t* output, const uint8_t* input, size_t len);
+ void decrypt(uint8_t* output, const uint8_t* input, size_t len);
+
+ void clear();
+
+protected:
+ CBCCommon();
+ void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; }
+
+private:
+ BlockCipher *blockCipher;
+ uint8_t iv[16];
+ uint8_t temp[16];
+ uint8_t posn;
+};
+
+template <typename T>
+class CBC : public CBCCommon
+{
+public:
+ CBC() { setBlockCipher(&cipher); }
+
+private:
+ T cipher;
+};
+
+#endif
\ No newline at end of file

diff --git a/lib/microReticulum/src/Cryptography/Ed25519.cpp b/lib/microReticulum/src/Cryptography/Ed25519.cpp
new file mode 100755
index 0000000..a69c397
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Ed25519.cpp
@@ -0,0 +1,3 @@
+#include "Ed25519.h"
+
+using namespace RNS::Cryptography;

diff --git a/lib/microReticulum/src/Cryptography/Ed25519.h b/lib/microReticulum/src/Cryptography/Ed25519.h
new file mode 100755
index 0000000..69cb396
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Ed25519.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include "Bytes.h"
+
+#include <Ed25519.h>
+
+#include <memory>
+
+/*
+
+Note that the library currently in use for Ed25519 does not support generating keys from a seed.
+
+*/
+
+namespace RNS { namespace Cryptography {
+
+ class Ed25519PublicKey {
+
+ public:
+ using Ptr = std::shared_ptr<Ed25519PublicKey>;
+
+ public:
+ Ed25519PublicKey(const Bytes& publicKey) {
+ _publicKey = publicKey;
+ }
+ ~Ed25519PublicKey() {}
+
+ public:
+ // creates a new instance with specified seed
+ static inline Ptr from_public_bytes(const Bytes& publicKey) {
+ return Ptr(new Ed25519PublicKey(publicKey));
+ }
+
+ inline const Bytes& public_bytes() {
+ return _publicKey;
+ }
+
+ inline bool verify(const Bytes& signature, const Bytes& message) {
+ return Ed25519::verify(signature.data(), _publicKey.data(), message.data(), message.size());
+ }
+
+ private:
+ Bytes _publicKey;
+
+ };
+
+ class Ed25519PrivateKey {
+
+ public:
+ using Ptr = std::shared_ptr<Ed25519PrivateKey>;
+
+ public:
+ Ed25519PrivateKey(const Bytes& privateKey) {
+ if (privateKey) {
+ // use specified private key
+ _privateKey = privateKey;
+ }
+ else {
+ // create random private key
+ Ed25519::generatePrivateKey(_privateKey.writable(32));
+ }
+ // derive public key from private key
+ Ed25519::derivePublicKey(_publicKey.writable(32), _privateKey.data());
+ }
+ ~Ed25519PrivateKey() {}
+
+ public:
+ // creates a new instance with a random seed
+ static inline Ptr generate() {
+ // CBA TODO determine why below is confused with (implicit) copy constructor
+ //return Ptr(new Ed25519PrivateKey({Bytes::NONE}));
+ return Ptr(new Ed25519PrivateKey(Bytes::NONE));
+ }
+
+ // creates a new instance with specified seed
+ static inline Ptr from_private_bytes(const Bytes& privateKey) {
+ return Ptr(new Ed25519PrivateKey(privateKey));
+ }
+
+ inline const Bytes& private_bytes() {
+ return _privateKey;
+ }
+
+ // creates a new instance of public key for this private key
+ inline Ed25519PublicKey::Ptr public_key() {
+ return Ed25519PublicKey::from_public_bytes(_publicKey);
+ }
+
+ inline const Bytes sign(const Bytes& message) {
+ //z return _sk.sign(message);
+ Bytes signature;
+ Ed25519::sign(signature.writable(64), _privateKey.data(), _publicKey.data(), message.data(), message.size());
+ return signature;
+ }
+
+ private:
+ Bytes _privateKey;
+ Bytes _publicKey;
+
+ };
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/Fernet.cpp b/lib/microReticulum/src/Cryptography/Fernet.cpp
new file mode 100755
index 0000000..f70bc6e
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Fernet.cpp
@@ -0,0 +1,116 @@
+#include "Fernet.h"
+
+#include "HMAC.h"
+#include "PKCS7.h"
+#include "AES.h"
+#include "../Log.h"
+
+#include <stdexcept>
+#include <time.h>
+
+using namespace RNS;
+using namespace RNS::Cryptography;
+
+Fernet::Fernet(const Bytes& key) {
+
+ if (!key) {
+ throw std::invalid_argument("Fernet key cannot be None");
+ }
+
+ if (key.size() != 32) {
+ throw std::invalid_argument("Fernet key must be 32 bytes, not " + std::to_string(key.size()));
+ }
+
+ //self._signing_key = key[:16]
+ _signing_key = key.left(16);
+ //self._encryption_key = key[16:]
+ _encryption_key = key.mid(16);
+
+ MEM("Fernet object created");
+}
+
+Fernet::~Fernet() {
+ MEM("Fernet object destroyed");
+}
+
+bool Fernet::verify_hmac(const Bytes& token) {
+
+ if (token.size() <= 32) {
+ throw std::invalid_argument("Cannot verify HMAC on token of only " + std::to_string(token.size()) + " bytes");
+ }
+
+ //received_hmac = token[-32:]
+ Bytes received_hmac = token.right(32);
+ DEBUG("Fernet::verify_hmac: received_hmac: " + received_hmac.toHex());
+ //expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
+ Bytes expected_hmac = HMAC::generate(_signing_key, token.left(token.size()-32))->digest();
+ DEBUG("Fernet::verify_hmac: expected_hmac: " + expected_hmac.toHex());
+
+ return (received_hmac == expected_hmac);
+}
+
+const Bytes Fernet::encrypt(const Bytes& data) {
+
+ DEBUG("Fernet::encrypt: plaintext length: " + std::to_string(data.size()));
+ Bytes iv = random(16);
+ //double current_time = OS::time();
+ TRACE("Fernet::encrypt: iv: " + iv.toHex());
+
+ TRACE("Fernet::encrypt: plaintext: " + data.toHex());
+ Bytes ciphertext = AES_128_CBC::encrypt(
+ PKCS7::pad(data),
+ _encryption_key,
+ iv
+ );
+ DEBUG("Fernet::encrypt: padded ciphertext length: " + std::to_string(ciphertext.size()));
+ TRACE("Fernet::encrypt: ciphertext: " + ciphertext.toHex());
+
+ Bytes signed_parts = iv + ciphertext;
+
+ //return signed_parts + HMAC::generate(_signing_key, signed_parts)->digest();
+ Bytes sig(HMAC::generate(_signing_key, signed_parts)->digest());
+ TRACE("Fernet::encrypt: sig: " + sig.toHex());
+ Bytes token(signed_parts + sig);
+ DEBUG("Fernet::encrypt: token length: " + std::to_string(token.size()));
+ return token;
+}
+
+
+const Bytes Fernet::decrypt(const Bytes& token) {
+
+ DEBUG("Fernet::decrypt: token length: " + std::to_string(token.size()));
+ if (token.size() < 48) {
+ throw std::invalid_argument("Cannot decrypt token of only " + std::to_string(token.size()) + " bytes");
+ }
+
+ if (!verify_hmac(token)) {
+ throw std::invalid_argument("Fernet token HMAC was invalid");
+ }
+
+ //iv = token[:16]
+ Bytes iv = token.left(16);
+ TRACE("Fernet::decrypt: iv: " + iv.toHex());
+
+ //ciphertext = token[16:-32]
+ Bytes ciphertext = token.mid(16, token.size()-48);
+ TRACE("Fernet::decrypt: ciphertext: " + ciphertext.toHex());
+
+ try {
+ Bytes plaintext = PKCS7::unpad(
+ AES_128_CBC::decrypt(
+ ciphertext,
+ _encryption_key,
+ iv
+ )
+ );
+ DEBUG("Fernet::encrypt: unpadded plaintext length: " + std::to_string(plaintext.size()));
+ TRACE("Fernet::decrypt: plaintext: " + plaintext.toHex());
+
+ DEBUG("Fernet::decrypt: plaintext length: " + std::to_string(plaintext.size()));
+ return plaintext;
+ }
+ catch (std::exception& e) {
+ WARNING("Could not decrypt Fernet token");
+ throw std::runtime_error("Could not decrypt Fernet token");
+ }
+}
\ No newline at end of file

diff --git a/lib/microReticulum/src/Cryptography/Fernet.h b/lib/microReticulum/src/Cryptography/Fernet.h
new file mode 100755
index 0000000..914e98a
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Fernet.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "Random.h"
+#include "../Bytes.h"
+
+#include <stdint.h>
+
+namespace RNS { namespace Cryptography {
+
+ /*
+ This class provides a slightly modified implementation of the Fernet spec
+ found at: https://github.com/fernet/spec/blob/master/Spec.md
+
+ According to the spec, a Fernet token includes a one byte VERSION and
+ eight byte TIMESTAMP field at the start of each token. These fields are
+ not relevant to Reticulum. They are therefore stripped from this
+ implementation, since they incur overhead and leak initiator metadata.
+ */
+ class Fernet {
+
+ public:
+ using Ptr = std::shared_ptr<Fernet>;
+
+ public:
+ static inline const Bytes generate_key() { return random(32); }
+
+ public:
+ Fernet(const Bytes& key);
+ ~Fernet();
+
+ public:
+ bool verify_hmac(const Bytes& token);
+ const Bytes encrypt(const Bytes& data);
+ const Bytes decrypt(const Bytes& token);
+
+ private:
+ Bytes _signing_key;
+ Bytes _encryption_key;
+ };
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/HKDF.cpp b/lib/microReticulum/src/Cryptography/HKDF.cpp
new file mode 100755
index 0000000..966756f
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/HKDF.cpp
@@ -0,0 +1,28 @@
+#include "HKDF.h"
+
+#include <HKDF.h>
+#include <SHA256.h>
+
+using namespace RNS;
+
+const Bytes RNS::Cryptography::hkdf(size_t length, const Bytes& derive_from, const Bytes& salt /*= {Bytes::NONE}*/, const Bytes& context /*= {Bytes::NONE}*/) {
+
+ if (length <= 0) {
+ throw std::invalid_argument("Invalid output key length");
+ }
+
+ if (!derive_from) {
+ throw std::invalid_argument("Cannot derive key from empty input material");
+ }
+
+ HKDF<SHA256> hkdf;
+ if (salt) {
+ hkdf.setKey(derive_from.data(), derive_from.size(), salt.data(), salt.size());
+ }
+ else {
+ hkdf.setKey(derive_from.data(), derive_from.size());
+ }
+ Bytes derived;
+ hkdf.extract(derived.writable(length), length);
+ return derived;
+}

diff --git a/lib/microReticulum/src/Cryptography/HKDF.h b/lib/microReticulum/src/Cryptography/HKDF.h
new file mode 100755
index 0000000..903c31b
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/HKDF.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "../Bytes.h"
+
+namespace RNS { namespace Cryptography {
+
+ const Bytes hkdf(size_t length, const Bytes& derive_from, const Bytes& salt = {Bytes::NONE}, const Bytes& context = {Bytes::NONE});
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/HMAC.h b/lib/microReticulum/src/Cryptography/HMAC.h
new file mode 100755
index 0000000..92a5bce
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/HMAC.h
@@ -0,0 +1,109 @@
+#pragma once
+
+#include "../Bytes.h"
+
+#include <Hash.h>
+#include <SHA256.h>
+#include <SHA512.h>
+#include <stdexcept>
+#include <memory>
+#include <cassert>
+
+namespace RNS { namespace Cryptography {
+
+ class HMAC {
+
+ public:
+ enum Digest {
+ DIGEST_NONE,
+ DIGEST_SHA256,
+ DIGEST_SHA512,
+ };
+
+ using Ptr = std::shared_ptr<HMAC>;
+
+ public:
+ /*
+ Create a new HMAC object.
+ key: bytes or buffer, key for the keyed hash object.
+ msg: bytes or buffer, Initial input for the hash or None.
+ digest: The underlying hash algorithm to use
+ */
+ HMAC(const Bytes& key, const Bytes& msg = {Bytes::NONE}, Digest digest = DIGEST_SHA256) {
+
+ if (digest == DIGEST_NONE) {
+ throw std::invalid_argument("Cannot derive key from empty input material");
+ }
+
+ switch (digest) {
+ case DIGEST_SHA256:
+ _hash = std::unique_ptr<Hash>(new SHA256());
+ break;
+ case DIGEST_SHA512:
+ _hash = std::unique_ptr<Hash>(new SHA512());
+ break;
+ default:
+ throw std::invalid_argument("Unknown ior unsuppored digest");
+ }
+
+ _key = key;
+ _hash->resetHMAC(key.data(), key.size());
+
+ if (msg) {
+ update(msg);
+ }
+ }
+
+ /*
+ Feed data from msg into this hashing object.
+ */
+ void update(const Bytes& msg) {
+ assert(_hash);
+ _hash->update(msg.data(), msg.size());
+ }
+
+ /*
+ Return the hash value of this hashing object.
+ This returns the hmac value as bytes. The object is
+ not altered in any way by this function; you can continue
+ updating the object after calling this function.
+ */
+ Bytes digest() {
+ assert(_hash);
+ Bytes result;
+ _hash->finalizeHMAC(_key.data(), _key.size(), result.writable(_hash->hashSize()), _hash->hashSize());
+ return result;
+ }
+
+ /*
+ Create a new hashing object and return it.
+ key: bytes or buffer, The starting key for the hash.
+ msg: bytes or buffer, Initial input for the hash, or None.
+ digest: The underlying hash algorithm to use.
+ You can now feed arbitrary bytes into the object using its update()
+ method, and can ask for the hash value at any time by calling its digest()
+ method.
+ */
+ static inline Ptr generate(const Bytes& key, const Bytes& msg = {Bytes::NONE}, Digest digest = DIGEST_SHA256) {
+ return Ptr(new HMAC(key, msg, digest));
+ }
+
+ private:
+ Bytes _key;
+ std::unique_ptr<Hash> _hash;
+
+ };
+
+ /*
+ Fast inline implementation of HMAC.
+ key: bytes or buffer, The key for the keyed hash object.
+ msg: bytes or buffer, Input message.
+ digest: The underlying hash algorithm to use.
+ */
+ inline const Bytes digest(const Bytes& key, const Bytes& msg, HMAC::Digest digest = HMAC::DIGEST_SHA256) {
+ HMAC hmac(key, msg, digest);
+ hmac.update(msg);
+ return hmac.digest();
+ }
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/Hashes.cpp b/lib/microReticulum/src/Cryptography/Hashes.cpp
new file mode 100755
index 0000000..5687efa
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Hashes.cpp
@@ -0,0 +1,36 @@
+#include "Hashes.h"
+
+#include "../Bytes.h"
+
+#include <SHA256.h>
+#include <SHA512.h>
+
+using namespace RNS;
+
+/*
+The SHA primitives are abstracted here to allow platform-
+aware hardware acceleration in the future. Currently only
+uses Python's internal SHA-256 implementation. All SHA-256
+calls in RNS end up here.
+*/
+
+const Bytes RNS::Cryptography::sha256(const Bytes& data) {
+ //TRACE("Cryptography::sha256: data: " + data.toHex() );
+ SHA256 digest;
+ digest.reset();
+ digest.update(data.data(), data.size());
+ Bytes hash;
+ digest.finalize(hash.writable(32), 32);
+ //TRACE("Cryptography::sha256: hash: " + hash.toHex() );
+ return hash;
+}
+
+const Bytes RNS::Cryptography::sha512(const Bytes& data) {
+ SHA512 digest;
+ digest.reset();
+ digest.update(data.data(), data.size());
+ Bytes hash;
+ digest.finalize(hash.writable(64), 64);
+ //TRACE("Cryptography::sha512: hash: " + hash.toHex() );
+ return hash;
+}

diff --git a/lib/microReticulum/src/Cryptography/Hashes.h b/lib/microReticulum/src/Cryptography/Hashes.h
new file mode 100755
index 0000000..c08df49
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Hashes.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "../Bytes.h"
+
+#include <stdint.h>
+
+namespace RNS { namespace Cryptography {
+
+ const Bytes sha256(const Bytes& data);
+ const Bytes sha512(const Bytes& data);
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/PKCS7.h b/lib/microReticulum/src/Cryptography/PKCS7.h
new file mode 100755
index 0000000..4167d31
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/PKCS7.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "../Bytes.h"
+//#include "../Log.h"
+
+#include <stdexcept>
+
+namespace RNS { namespace Cryptography {
+
+ class PKCS7 {
+
+ public:
+
+ static const size_t BLOCKSIZE = 16;
+
+ static inline const Bytes pad(const Bytes& data, size_t bs = BLOCKSIZE) {
+ Bytes padded(data);
+ inplace_pad(padded, bs);
+ return padded;
+ }
+
+ static inline const Bytes unpad(const Bytes& data, size_t bs = BLOCKSIZE) {
+ Bytes unpadded(data);
+ inplace_unpad(unpadded, bs);
+ return unpadded;
+ }
+
+ // updates passed buffer
+ static inline void inplace_pad(Bytes& data, size_t bs = BLOCKSIZE) {
+ size_t len = data.size();
+ //DEBUG("PKCS7::pad: len: " + std::to_string(len));
+ size_t padlen = bs - (len % bs);
+ //DEBUG("PKCS7::pad: pad len: " + std::to_string(padlen));
+ // create zero-filled byte padding array of size padlen
+ //p v = bytes([padlen])
+ //uint8_t pad[padlen] = {0};
+ uint8_t pad[padlen];
+ memset(pad, 0, padlen);
+ // set last byte of padding array to size of padding
+ pad[padlen-1] = (uint8_t)padlen;
+ // concatenate data with padding
+ //p return data+v*padlen
+ data.append(pad, padlen);
+ //DEBUG("PKCS7::pad: data size: " + std::to_string(data.size()));
+ }
+
+ // updates passed buffer
+ static inline void inplace_unpad(Bytes& data, size_t bs = BLOCKSIZE) {
+ size_t len = data.size();
+ //DEBUG("PKCS7::unpad: len: " + std::to_string(len));
+ // read last byte which is pad length
+ //pad = data[-1]
+ size_t padlen = (size_t)data.data()[data.size()-1];
+ //DEBUG("PKCS7::unpad: pad len: " + std::to_string(padlen));
+ if (padlen > bs) {
+ throw std::runtime_error("Cannot unpad, invalid padding length of " + std::to_string(padlen) + " bytes");
+ }
+ // truncate data to strip padding
+ //return data[:len-padlen]
+ data.resize(len - padlen);
+ //DEBUG("PKCS7::unpad: data size: " + std::to_string(data.size()));
+ }
+
+ };
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/Random.h b/lib/microReticulum/src/Cryptography/Random.h
new file mode 100755
index 0000000..3534c9c
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Random.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "../Bytes.h"
+
+#include <RNG.h>
+#include <stdint.h>
+
+namespace RNS { namespace Cryptography {
+
+ // return vector specified length of random bytes
+ inline const Bytes random(size_t length) {
+ Bytes rand;
+ RNG.rand(rand.writable(length), length);
+ return rand;
+ }
+
+ // return 32 bit random unigned int
+ inline uint32_t randomnum() {
+ Bytes rand;
+ RNG.rand(rand.writable(4), 4);
+ uint32_t randnum = uint32_t((unsigned char)(rand.data()[0]) << 24 |
+ (unsigned char)(rand.data()[0]) << 16 |
+ (unsigned char)(rand.data()[0]) << 8 |
+ (unsigned char)(rand.data()[0]));
+ return randnum;
+ }
+
+ // return 32 bit random unsigned int between 0 and specified value
+ inline uint32_t randomnum(uint32_t max) {
+ return randomnum() % max;
+ }
+
+ // return random float value from 0 to 1
+ inline float random() {
+ return (float)(randomnum() / (float)0xffffffff);
+ }
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/Token.cpp b/lib/microReticulum/src/Cryptography/Token.cpp
new file mode 100755
index 0000000..e91c7e8
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Token.cpp
@@ -0,0 +1,159 @@
+#include "Token.h"
+
+#include "HMAC.h"
+#include "PKCS7.h"
+#include "AES.h"
+#include "../Log.h"
+
+#include <stdexcept>
+#include <time.h>
+
+using namespace RNS;
+using namespace RNS::Cryptography;
+using namespace RNS::Type::Cryptography::Token;
+
+Token::Token(const Bytes& key, token_mode mode /*= AES*/) {
+
+ if (!key) {
+ throw std::invalid_argument("Token key cannot be None");
+ }
+
+ if (mode == MODE_AES) {
+ if (key.size() == 32) {
+ _mode = MODE_AES_128_CBC;
+ //p self._signing_key = key[:16]
+ _signing_key = key.left(16);
+ //p self._encryption_key = key[16:]
+ _encryption_key = key.mid(16);
+ }
+ else if (key.size() == 64) {
+ _mode = MODE_AES_256_CBC;
+ //p self._signing_key = key[:32]
+ _signing_key = key.left(32);
+ //p self._encryption_key = key[32:]
+ _encryption_key = key.mid(32);
+ }
+ else {
+ throw std::invalid_argument("Token key must be 128 or 256 bits, not " + std::to_string(key.size()*8));
+ }
+ }
+ else {
+ throw std::invalid_argument("Invalid token mode: " + std::to_string(mode));
+ }
+
+ MEM("Token object created");
+}
+
+Token::~Token() {
+ MEM("Token object destroyed");
+}
+
+bool Token::verify_hmac(const Bytes& token) {
+
+ if (token.size() <= 32) {
+ throw std::invalid_argument("Cannot verify HMAC on token of only " + std::to_string(token.size()) + " bytes");
+ }
+
+ //received_hmac = token[-32:]
+ Bytes received_hmac = token.right(32);
+ DEBUG("Token::verify_hmac: received_hmac: " + received_hmac.toHex());
+ //expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
+ Bytes expected_hmac = HMAC::generate(_signing_key, token.left(token.size()-32))->digest();
+ DEBUG("Token::verify_hmac: expected_hmac: " + expected_hmac.toHex());
+
+ return (received_hmac == expected_hmac);
+}
+
+const Bytes Token::encrypt(const Bytes& data) {
+
+ DEBUG("Token::encrypt: plaintext length: " + std::to_string(data.size()));
+ Bytes iv = random(16);
+ //double current_time = OS::time();
+ TRACE("Token::encrypt: iv: " + iv.toHex());
+
+ TRACE("Token::encrypt: plaintext: " + data.toHex());
+ Bytes ciphertext;
+ if (_mode == MODE_AES_128_CBC) {
+ ciphertext = AES_128_CBC::encrypt(
+ PKCS7::pad(data),
+ _encryption_key,
+ iv
+ );
+ }
+ else if (_mode == MODE_AES_256_CBC) {
+ ciphertext = AES_256_CBC::encrypt(
+ PKCS7::pad(data),
+ _encryption_key,
+ iv
+ );
+ }
+ else {
+ throw new std::invalid_argument("Invalid token mode "+std::to_string(_mode));
+ }
+ DEBUG("Token::encrypt: padded ciphertext length: " + std::to_string(ciphertext.size()));
+ TRACE("Token::encrypt: ciphertext: " + ciphertext.toHex());
+
+ Bytes signed_parts = iv + ciphertext;
+
+ //return signed_parts + HMAC::generate(_signing_key, signed_parts)->digest();
+ Bytes sig(HMAC::generate(_signing_key, signed_parts)->digest());
+ TRACE("Token::encrypt: sig: " + sig.toHex());
+ Bytes token(signed_parts + sig);
+ DEBUG("Token::encrypt: token length: " + std::to_string(token.size()));
+ return token;
+}
+
+
+const Bytes Token::decrypt(const Bytes& token) {
+
+ DEBUG("Token::decrypt: token length: " + std::to_string(token.size()));
+ if (token.size() < 48) {
+ throw std::invalid_argument("Cannot decrypt token of only " + std::to_string(token.size()) + " bytes");
+ }
+
+ if (!verify_hmac(token)) {
+ throw std::invalid_argument("Token token HMAC was invalid");
+ }
+
+ //iv = token[:16]
+ Bytes iv = token.left(16);
+ TRACE("Token::decrypt: iv: " + iv.toHex());
+
+ //ciphertext = token[16:-32]
+ Bytes ciphertext = token.mid(16, token.size()-48);
+ TRACE("Token::decrypt: ciphertext: " + ciphertext.toHex());
+
+ try {
+ Bytes plaintext;
+ if (_mode == MODE_AES_128_CBC) {
+ plaintext = PKCS7::unpad(
+ AES_128_CBC::decrypt(
+ ciphertext,
+ _encryption_key,
+ iv
+ )
+ );
+ }
+ else if (_mode == MODE_AES_256_CBC) {
+ plaintext = PKCS7::unpad(
+ AES_256_CBC::decrypt(
+ ciphertext,
+ _encryption_key,
+ iv
+ )
+ );
+ }
+ else {
+ throw new std::invalid_argument("Invalid token mode "+std::to_string(_mode));
+ }
+ DEBUG("Token::encrypt: unpadded plaintext length: " + std::to_string(plaintext.size()));
+ TRACE("Token::decrypt: plaintext: " + plaintext.toHex());
+
+ DEBUG("Token::decrypt: plaintext length: " + std::to_string(plaintext.size()));
+ return plaintext;
+ }
+ catch (std::exception& e) {
+ WARNING("Could not decrypt Token token");
+ throw std::runtime_error("Could not decrypt Token token");
+ }
+}
\ No newline at end of file

diff --git a/lib/microReticulum/src/Cryptography/Token.h b/lib/microReticulum/src/Cryptography/Token.h
new file mode 100755
index 0000000..3d3f343
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/Token.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "Random.h"
+#include "../Bytes.h"
+#include "../Type.h"
+
+#include <stdint.h>
+
+namespace RNS { namespace Cryptography {
+
+ /*
+ This class provides a slightly modified implementation of the Fernet spec
+ found at: https://github.com/fernet/spec/blob/master/Spec.md
+
+ According to the spec, a Fernet token includes a one byte VERSION and
+ eight byte TIMESTAMP field at the start of each token. These fields are
+ not relevant to Reticulum. They are therefore stripped from this
+ implementation, since they incur overhead and leak initiator metadata.
+ */
+ class Token {
+
+ public:
+ using Ptr = std::shared_ptr<Token>;
+
+ public:
+ static inline const Bytes generate_key(RNS::Type::Cryptography::Token::token_mode mode = RNS::Type::Cryptography::Token::MODE_AES_256_CBC) {
+ if (mode == RNS::Type::Cryptography::Token::MODE_AES_128_CBC) return random(32);
+ else if (mode == RNS::Type::Cryptography::Token::MODE_AES_256_CBC) return random(64);
+ else throw new std::invalid_argument("Invalid token mode: " + std::to_string(mode));
+ }
+
+ public:
+ Token(const Bytes& key, RNS::Type::Cryptography::Token::token_mode mode = RNS::Type::Cryptography::Token::MODE_AES);
+ ~Token();
+
+ public:
+ bool verify_hmac(const Bytes& token);
+ const Bytes encrypt(const Bytes& data);
+ const Bytes decrypt(const Bytes& token);
+
+ private:
+ RNS::Type::Cryptography::Token::token_mode _mode = RNS::Type::Cryptography::Token::MODE_AES_256_CBC;
+ Bytes _signing_key;
+ Bytes _encryption_key;
+ };
+
+} }

diff --git a/lib/microReticulum/src/Cryptography/X25519.cpp b/lib/microReticulum/src/Cryptography/X25519.cpp
new file mode 100755
index 0000000..5e7cc31
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/X25519.cpp
@@ -0,0 +1,3 @@
+#include "X25519.h"
+
+using namespace RNS::Cryptography;

diff --git a/lib/microReticulum/src/Cryptography/X25519.h b/lib/microReticulum/src/Cryptography/X25519.h
new file mode 100755
index 0000000..75c009b
--- /dev/null
+++ b/lib/microReticulum/src/Cryptography/X25519.h
@@ -0,0 +1,203 @@
+#pragma once
+
+#include "Bytes.h"
+#include "Log.h"
+
+#include <Curve25519.h>
+
+#include <memory>
+#include <stdexcept>
+#include <stdint.h>
+
+namespace RNS { namespace Cryptography {
+
+ class X25519PublicKey {
+
+ public:
+ using Ptr = std::shared_ptr<X25519PublicKey>;
+
+ public:
+/*
+ X25519PublicKey(const Bytes& x) {
+ _x = x;
+ }
+*/
+ X25519PublicKey(const Bytes& publicKey) {
+ _publicKey = publicKey;
+ }
+ ~X25519PublicKey() {}
+
+ public:
+ // creates a new instance with specified seed
+/*
+ static inline Ptr from_public_bytes(const Bytes& data) {
+ return Ptr(new X25519PublicKey(_unpack_number(data)));
+ }
+*/
+ static inline Ptr from_public_bytes(const Bytes& publicKey) {
+ return Ptr(new X25519PublicKey(publicKey));
+ }
+
+/*
+ Bytes public_bytes() {
+ return _pack_number(_x);
+ }
+*/
+ Bytes public_bytes() {
+ return _publicKey;
+ }
+
+ private:
+ //Bytes _x;
+ Bytes _publicKey;
+
+ };
+
+ class X25519PrivateKey {
+
+ public:
+ const float MIN_EXEC_TIME = 2; // in milliseconds
+ const float MAX_EXEC_TIME = 500; // in milliseconds
+ const uint8_t DELAY_WINDOW = 10;
+
+ //z T_CLEAR = None
+ const uint8_t T_MAX = 0;
+
+ using Ptr = std::shared_ptr<X25519PrivateKey>;
+
+ public:
+/*
+ X25519PrivateKey(const Bytes& a) {
+ _a = a;
+ }
+*/
+ X25519PrivateKey(const Bytes& privateKey) {
+ if (privateKey) {
+ // use specified private key
+ _privateKey = privateKey;
+ // similar to derive public key from private key
+ // second param "f" is secret
+ //eval(uint8_t result[32], const uint8_t s[32], const uint8_t x[32])
+ // derive public key from private key
+ Curve25519::eval(_publicKey.writable(32), _privateKey.data(), 0);
+ }
+ else {
+ // create random private key and derive public key
+ // second param "f" is secret
+ //dh1(uint8_t k[32], uint8_t f[32])
+ Curve25519::dh1(_publicKey.writable(32), _privateKey.writable(32));
+ }
+ }
+ ~X25519PrivateKey() {}
+
+ public:
+ // creates a new instance with a random seed
+/*
+ static inline Ptr generate() {
+ return from_private_bytes(os.urandom(32));
+ }
+*/
+ static inline Ptr generate() {
+ return from_private_bytes({Bytes::NONE});
+ }
+
+ // creates a new instance with specified seed
+/*
+ static inline Ptr from_private_bytes(const Bytes& data) {
+ return Ptr(new X25519PrivateKey(_fix_secret(_unpack_number(data))));
+ }
+*/
+ static inline Ptr from_private_bytes(const Bytes& privateKey) {
+ return Ptr(new X25519PrivateKey(privateKey));
+ }
+
+/*
+ inline const Bytes private_bytes() {
+ return _pack_number(_a);
+ }
+*/
+ inline const Bytes& private_bytes() {
+ return _privateKey;
+ }
+
+ // creates a new instance of public key for this private key
+/*
+ inline X25519PublicKey::Ptr public_key() {
+ return X25519PublicKey::from_public_bytes(_pack_number(_raw_curve25519(9, _a)));
+ }
+*/
+ inline X25519PublicKey::Ptr public_key() {
+ return X25519PublicKey::from_public_bytes(_publicKey);
+ }
+
+/*
+ inline const Bytes exchange(const Bytes& peer_public_key) {
+ if isinstance(peer_public_key, bytes):
+ peer_public_key = X25519PublicKey.from_public_bytes(peer_public_key)
+
+ start = OS::time()
+
+ shared = _pack_number(_raw_curve25519(peer_public_key.x, _a))
+
+ end = OS::time()
+ duration = end-start
+
+ if X25519PrivateKey.T_CLEAR == None:
+ X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
+
+ if end > X25519PrivateKey.T_CLEAR:
+ X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
+ X25519PrivateKey.T_MAX = 0
+
+ if duration < X25519PrivateKey.T_MAX or duration < X25519PrivateKey.MIN_EXEC_TIME:
+ target = start+X25519PrivateKey.T_MAX
+
+ if target > start+X25519PrivateKey.MAX_EXEC_TIME:
+ target = start+X25519PrivateKey.MAX_EXEC_TIME
+
+ if target < start+X25519PrivateKey.MIN_EXEC_TIME:
+ target = start+X25519PrivateKey.MIN_EXEC_TIME
+
+ try:
+ OS::sleep(target-OS::time())
+ except Exception as e:
+ pass
+
+ elif duration > X25519PrivateKey.T_MAX:
+ X25519PrivateKey.T_MAX = duration
+
+ return shared
+ }
+*/
+ inline const Bytes exchange(const Bytes& peer_public_key) {
+ DEBUG("X25519PublicKey::exchange: public key: " + _publicKey.toHex());
+ DEBUG("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex());
+ DEBUG("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex());
+ Bytes sharedKey;
+ if (!Curve25519::eval(sharedKey.writable(32), _privateKey.data(), peer_public_key.data())) {
+ throw std::runtime_error("Peer key is invalid");
+ }
+ DEBUG("X25519PublicKey::exchange: shared key: " + sharedKey.toHex());
+ DEBUG("X25519PublicKey::exchange: post private key: " + _privateKey.toHex());
+ return sharedKey;
+ }
+
+ inline bool verify(const Bytes& peer_public_key) {
+ DEBUG("X25519PublicKey::exchange: public key: " + _publicKey.toHex());
+ DEBUG("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex());
+ DEBUG("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex());
+ Bytes sharedKey(peer_public_key);
+ bool success = Curve25519::dh2(sharedKey.writable(32), _privateKey.writable(32));
+ DEBUG("X25519PublicKey::exchange: shared key: " + sharedKey.toHex());
+ DEBUG("X25519PublicKey::exchange: post private key: " + _privateKey.toHex());
+ return success;
+ }
+
+ private:
+ //Bytes _a;
+ Bytes _privateKey;
+ Bytes _publicKey;
+
+ };
+
+} }

diff --git a/lib/microReticulum/src/Destination.cpp b/lib/microReticulum/src/Destination.cpp
new file mode 100755
index 0000000..d15a460
--- /dev/null
+++ b/lib/microReticulum/src/Destination.cpp
@@ -0,0 +1,486 @@
+#include "Destination.h"
+
+#include "Transport.h"
+#include "Interface.h"
+#include "Packet.h"
+#include "Log.h"
+
+#include <vector>
+#include <time.h>
+#include <string.h>
+
+using namespace RNS;
+using namespace RNS::Type::Destination;
+using namespace RNS::Utilities;
+
+Destination::Destination(const Identity& identity, const directions direction, const types type, const char* app_name, const char* aspects) : _object(new Object(identity)) {
+ assert(_object);
+ MEM("Destination object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+
+ // Check input values and build name string
+ if (strchr(app_name, '.') != nullptr) {
+ throw std::invalid_argument("Dots can't be used in app names");
+ }
+ //TRACE("Destination::Destination: app name: " + std::string(app_name));
+
+ _object->_type = type;
+ _object->_direction = direction;
+
+ std::string fullaspects(aspects);
+ if (!identity && direction == IN && _object->_type != PLAIN) {
+ TRACE("Destination::Destination: identity not provided, creating new one");
+ _object->_identity = Identity();
+ // CBA TODO determine why identity.hexhash is added both here and by expand_name called below
+ fullaspects += "." + _object->_identity.hexhash();
+ }
+ //TRACE("Destination::Destination: full aspects: " + fullaspects);
+
+ if (_object->_identity && _object->_type == PLAIN) {
+ throw std::invalid_argument("Selected destination type PLAIN cannot hold an identity");
+ }
+
+ _object->_name = expand_name(_object->_identity, app_name, fullaspects.c_str());
+ //TRACE("Destination::Destination: name: " + _object->_name);
+
+ // Generate the destination address hash
+ //TRACE("Destination::Destination: creating hash...");
+ _object->_hash = hash(_object->_identity, app_name, fullaspects.c_str());
+ _object->_hexhash = _object->_hash.toHex();
+ TRACE("Destination::Destination: hash: " + _object->_hash.toHex());
+ //TRACE("Destination::Destination: creating name hash...");
+ //p self.name_hash = RNS.Identity.full_hash(self.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
+ _object->_name_hash = name_hash(app_name, aspects);
+ //TRACE("Destination::Destination: name hash: " + _object->_name_hash.toHex());
+
+ //TRACE("Destination::Destination: calling register_destination");
+ Transport::register_destination(*this);
+
+ MEM("Destination object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+}
+
+Destination::Destination(const Identity& identity, const Type::Destination::directions direction, const Type::Destination::types type, const Bytes& hash) : _object(new Object(identity)) {
+ assert(_object);
+ MEM("Destination object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+
+ _object->_type = type;
+ _object->_direction = direction;
+
+ if (_object->_identity && _object->_type == PLAIN) {
+ throw std::invalid_argument("Selected destination type PLAIN cannot hold an identity");
+ }
+
+ _object->_hash = hash;
+ _object->_hexhash = _object->_hash.toHex();
+ TRACE("Destination::Destination: hash: " + _object->_hash.toHex());
+ //TRACE("Destination::Destination: creating name hash...");
+ //p self.name_hash = RNS.Identity.full_hash(self.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
+ _object->_name_hash = name_hash("unknown", "unknown");
+ //TRACE("Destination::Destination: name hash: " + _object->_name_hash.toHex());
+
+ //TRACE("Destination::Destination: calling register_destination");
+ Transport::register_destination(*this);
+
+ MEM("Destination object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+}
+
+/*virtual*/ Destination::~Destination() {
+ MEM("Destination object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ if (_object && _object.use_count() == 1) {
+ MEM("Destination object has last data reference");
+
+ // CBA Can't call deregister_destination here because it's possible (likely even) that Destination
+ // is being destructed from that same collection which will result in a llop and memory errors.
+ //TRACE("Destination::~Destination: calling deregister_destination");
+ //Transport::deregister_destination(*this);
+ }
+}
+
+/*
+:returns: A destination name in adressable hash form, for an app_name and a number of aspects.
+*/
+/*static*/ Bytes Destination::hash(const Identity& identity, const char* app_name, const char* aspects) {
+ //p name_hash = Identity::full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
+ //p addr_hash_material = name_hash
+ Bytes addr_hash_material = name_hash(app_name, aspects);
+ if (identity) {
+ addr_hash_material << identity.hash();
+ }
+
+ //p return RNS.Identity.full_hash(addr_hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
+ // CBA TODO valid alternative?
+ //return Identity::full_hash(addr_hash_material).left(Type::Reticulum::TRUNCATED_HASHLENGTH/8);
+ return Identity::truncated_hash(addr_hash_material);
+}
+
+/*
+:returns: A name in hash form, for an app_name and a number of aspects.
+*/
+/*static*/ Bytes Destination::name_hash(const char* app_name, const char* aspects) {
+ //p name_hash = Identity::full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
+ return Identity::full_hash(expand_name({Type::NONE}, app_name, aspects)).left(Type::Identity::NAME_HASH_LENGTH/8);
+}
+
+/*
+:returns: A tuple containing the app name and a list of aspects, for a full-name string.
+*/
+/*static*/ std::vector<std::string> Destination::app_and_aspects_from_name(const char* full_name) {
+ //p components = full_name.split(".")
+ //p return (components[0], components[1:])
+ std::vector<std::string> components;
+ std::string name(full_name);
+ std::size_t pos = name.find('.');
+ components.push_back(name.substr(0, pos));
+ if (pos != std::string::npos) {
+ components.push_back(name.substr(pos+1));
+ }
+ return components;
+}
+
+/*
+:returns: A destination name in adressable hash form, for a full name string and Identity instance.
+*/
+/*static*/ Bytes Destination::hash_from_name_and_identity(const char* full_name, const Identity& identity) {
+ //p app_name, aspects = Destination.app_and_aspects_from_name(full_name)
+ //p return Destination.hash(identity, app_name, *aspects)
+ std::vector<std::string> components = app_and_aspects_from_name(full_name);
+ if (components.size() == 0) {
+ return {Bytes::NONE};
+ }
+ if (components.size() == 1) {
+ return hash(identity, components[0].c_str(), "");
+ }
+ return hash(identity, components[0].c_str(), components[1].c_str());
+}
+
+/*
+:returns: A string containing the full human-readable name of the destination, for an app_name and a number of aspects.
+*/
+/*static*/ std::string Destination::expand_name(const Identity& identity, const char* app_name, const char* aspects) {
+
+ if (strchr(app_name, '.') != nullptr) {
+ throw std::invalid_argument("Dots can't be used in app names");
+ }
+
+ std::string name(app_name);
+
+ if (aspects != nullptr) {
+ name += std::string(".") + aspects;
+ }
+
+ if (identity) {
+ name += "." + identity.hexhash();
+ }
+
+ return name;
+}
+
+/*
+Creates an announce packet for this destination and broadcasts it on all
+relevant interfaces. Application specific data can be added to the announce.
+
+:param app_data: *bytes* containing the app_data.
+:param path_response: Internal flag used by :ref:`RNS.Transport<api-transport>`. Ignore.
+*/
+//Packet Destination::announce(const Bytes& app_data /*= {}*/, bool path_response /*= false*/, const Interface& attached_interface /*= {Type::NONE}*/, const Bytes& tag /*= {}*/, bool send /*= true*/) {
+Packet Destination::announce(const Bytes& app_data, bool path_response, const Interface& attached_interface, const Bytes& tag /*= {}*/, bool send /*= true*/) {
+ assert(_object);
+ TRACE("Destination::announce: announcing destination...");
+
+ if (_object->_type != SINGLE) {
+ throw std::invalid_argument("Only SINGLE destination types can be announced");
+ }
+
+ if (_object->_direction != IN) {
+ throw std::invalid_argument("Only IN destination types can be announced");
+ }
+
+ double now = OS::time();
+ auto it = _object->_path_responses.begin();
+ while (it != _object->_path_responses.end()) {
+ // vector
+ //Response& entry = *it;
+ // map
+ PathResponse& entry = (*it).second;
+ if (now > (entry.first + PR_TAG_WINDOW)) {
+ it = _object->_path_responses.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+
+ Bytes announce_data;
+
+/*
+ // CBA TEST
+ TRACE("Destination::announce: performing path test...");
+ TRACE("Destination::announce: inserting path...");
+ _object->_path_responses.insert({Bytes("foo_tag"), {0, Bytes("this is foo tag")}});
+ TRACE("Destination::announce: inserting path...");
+ _object->_path_responses.insert({Bytes("test_tag"), {0, Bytes("this is test tag")}});
+ if (path_response) {
+ TRACE("Destination::announce: path_response is true");
+ }
+ if (!tag.empty()) {
+ TRACE("Destination::announce: tag is specified");
+ std::string tagstr((const char*)tag.data(), tag.size());
+ DEBUG(std::string("Destination::announce: tag: ") + tagstr);
+ DEBUG(std::string("Destination::announce: tag len: ") + std::to_string(tag.size()));
+ TRACE("Destination::announce: searching for tag...");
+ if (_object->_path_responses.find(tag) != _object->_path_responses.end()) {
+ TRACE("Destination::announce: found tag in _path_responses");
+ DEBUG(std::string("Destination::announce: data: ") +_object->_path_responses[tag].second.toString());
+ }
+ else {
+ TRACE("Destination::announce: tag not found in _path_responses");
+ }
+ }
+ TRACE("Destination::announce: path test finished");
+*/
+
+ if (path_response && !tag.empty() && _object->_path_responses.find(tag) != _object->_path_responses.end()) {
+ // This code is currently not used, since Transport will block duplicate
+ // path requests based on tags. When multi-path support is implemented in
+ // Transport, this will allow Transport to detect redundant paths to the
+ // same destination, and select the best one based on chosen criteria,
+ // since it will be able to detect that a single emitted announce was
+ // received via multiple paths. The difference in reception time will
+ // potentially also be useful in determining characteristics of the
+ // multiple available paths, and to choose the best one.
+ //z TRACE("Using cached announce data for answering path request with tag "+RNS.prettyhexrep(tag));
+ announce_data << _object->_path_responses[tag].second;
+ }
+ else {
+ Bytes destination_hash = _object->_hash;
+ //p random_hash = Identity::get_random_hash()[0:5] << int(time.time()).to_bytes(5, "big")
+ // CBA TODO add in time to random hash
+ Bytes random_hash = Cryptography::random(Type::Identity::RANDOM_HASH_LENGTH/8);
+
+ Bytes new_app_data(app_data);
+ if (new_app_data.empty() && !_object->_default_app_data.empty()) {
+ new_app_data = _object->_default_app_data;
+ }
+
+ Bytes signed_data;
+ //TRACE("Destination::announce: hash: " + _object->_hash.toHex());
+ //TRACE("Destination::announce: public key: " + _object->_identity.get_public_key().toHex());
+ //TRACE("Destination::announce: name hash: " + _object->_name_hash.toHex());
+ //TRACE("Destination::announce: random hash: " + random_hash.toHex());
+ //TRACE("Destination::announce: app data: " + new_app_data.toHex());
+ //TRACE("Destination::announce: app data text:" + new_app_data.toString());
+ signed_data << _object->_hash << _object->_identity.get_public_key() << _object->_name_hash << random_hash;
+ if (new_app_data) {
+ signed_data << new_app_data;
+ }
+ //TRACE("Destination::announce: signed data: " + signed_data.toHex());
+
+ Bytes signature(_object->_identity.sign(signed_data));
+ //TRACE("Destination::announce: signature: " + signature.toHex());
+
+ announce_data << _object->_identity.get_public_key() << _object->_name_hash << random_hash << signature;
+
+ if (new_app_data) {
+ announce_data << new_app_data;
+ }
+
+ // CBA ACCUMULATES
+ _object->_path_responses.insert({tag, {OS::time(), announce_data}});
+ }
+ //TRACE("Destination::announce: announce_data:" + announce_data.toHex());
+
+ Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
+ if (path_response) {
+ announce_context = Type::Packet::PATH_RESPONSE;
+ }
+
+ //TRACE("Destination::announce: creating announce packet...");
+ //p announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface)
+ //Packet announce_packet(*this, announce_data, Type::Packet::ANNOUNCE, announce_context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, nullptr, attached_interface);
+ Packet announce_packet(*this, attached_interface, announce_data, Type::Packet::ANNOUNCE, announce_context, Type::Transport::BROADCAST, Type::Packet::HEADER_1);
+
+ if (send) {
+ TRACE("Destination::announce: sending announce packet...");
+ announce_packet.send();
+ return {Type::NONE};
+ }
+ else {
+ return announce_packet;
+ }
+}
+
+Packet Destination::announce(const Bytes& app_data /*= {}*/, bool path_response /*= false*/) {
+ return announce(app_data, path_response, {Type::NONE});
+}
+
+
+/*
+Registers a request handler.
+
+:param path: The path for the request handler to be registered.
+:param response_generator: A function or method with the signature *response_generator(path, data, request_id, link_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
+:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list.
+:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity<api-identity>` hashes.
+:raises: ``ValueError`` if any of the supplied arguments are invalid.
+*/
+/*
+void Destination::register_request_handler(const Bytes& path, response_generator = None, request_policies allow = ALLOW_NONE, allowed_list = None) {
+ if path == None or path == "":
+ raise ValueError("Invalid path specified")
+ elif not callable(response_generator):
+ raise ValueError("Invalid response generator specified")
+ elif not allow in Destination.request_policies:
+ raise ValueError("Invalid request policy")
+ else:
+ path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
+ request_handler = [path, response_generator, allow, allowed_list]
+ self.request_handlers[path_hash] = request_handler
+}
+*/
+
+/*
+Deregisters a request handler.
+
+:param path: The path for the request handler to be deregistered.
+:returns: True if the handler was deregistered, otherwise False.
+*/
+/*
+bool Destination::deregister_request_handler(const Bytes& path) {
+ path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
+ if path_hash in self.request_handlers:
+ self.request_handlers.pop(path_hash)
+ return True
+ else:
+ return False
+}
+*/
+
+void Destination::receive(const Packet& packet) {
+ assert(_object);
+ if (packet.packet_type() == Type::Packet::LINKREQUEST) {
+ Bytes plaintext(packet.data());
+ incoming_link_request(plaintext, packet);
+ }
+ else {
+ // CBA TODO Why isn't the Packet decrypting itself?
+ Bytes plaintext(decrypt(packet.data()));
+ //TRACE("Destination::receive: decrypted data: " + plaintext.toHex());
+ if (plaintext) {
+ if (packet.packet_type() == Type::Packet::DATA) {
+ if (_object->_callbacks._packet) {
+ try {
+ _object->_callbacks._packet(plaintext, packet);
+ }
+ catch (std::exception& e) {
+ DEBUG("Error while executing receive callback from " + toString() + ". The contained exception was: " + e.what());
+ }
+ }
+ }
+ }
+ }
+}
+
+void Destination::incoming_link_request(const Bytes& data, const Packet& packet) {
+ assert(_object);
+ if (_object->_accept_link_requests) {
+TRACE("***** Accepting link request");
+ RNS::Link link = Link::validate_request(*this, data, packet);
+ if (link) {
+ _object->_links.insert(link);
+ }
+ }
+}
+
+/*
+Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
+
+:param plaintext: A *bytes-like* containing the plaintext to be encrypted.
+:raises: ``ValueError`` if destination does not hold a necessary key for encryption.
+*/
+/*virtual*/ const Bytes Destination::encrypt(const Bytes& data) {
+ assert(_object);
+ TRACE("Destination::encrypt: encrypting data...");
+
+ if (_object->_type == PLAIN) {
+ return data;
+ }
+
+ if (_object->_type == SINGLE && _object->_identity) {
+ return _object->_identity.encrypt(data);
+ }
+
+// TODO
+/*
+ if (_object->_type == GROUP {
+ if hasattr(self, "prv") and self.prv != None:
+ try:
+ return self.prv.encrypt(plaintext)
+ except Exception as e:
+ RNS.log("The GROUP destination could not encrypt data", RNS.LOG_ERROR)
+ RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
+ else:
+ raise ValueError("No private key held by GROUP destination. Did you create or load one?")
+ }
+*/
+
+ // CBA Reference implementation does not handle this default case
+ // CBA TODO Determine of returning plaintext is appropriate here (for now it's necessary for PROOF packets)
+ return data;
+}
+
+/*
+Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
+
+:param ciphertext: *Bytes* containing the ciphertext to be decrypted.
+:raises: ``ValueError`` if destination does not hold a necessary key for decryption.
+*/
+/*virtual*/ const Bytes Destination::decrypt(const Bytes& data) {
+ assert(_object);
+ TRACE("Destination::decrypt: decrypting data...");
+
+ if (_object->_type == PLAIN) {
+ return data;
+ }
+
+ if (_object->_type == SINGLE && _object->_identity) {
+ return _object->_identity.decrypt(data);
+ }
+
+/*
+ if (_object->_type == GROUP) {
+ if hasattr(self, "prv") and self.prv != None:
+ try:
+ return self.prv.decrypt(ciphertext)
+ except Exception as e:
+ RNS.log("The GROUP destination could not decrypt data", RNS.LOG_ERROR)
+ RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
+ else:
+ raise ValueError("No private key held by GROUP destination. Did you create or load one?")
+ }
+*/
+ // MOCK
+ return {Bytes::NONE};
+}
+
+/*
+Signs information for ``RNS.Destination.SINGLE`` type destination.
+
+:param message: *Bytes* containing the message to be signed.
+:returns: A *bytes-like* containing the message signature, or *None* if the destination could not sign the message.
+*/
+/*virtual*/ const Bytes Destination::sign(const Bytes& message) {
+ assert(_object);
+ if (_object->_type == SINGLE && _object->_identity) {
+ return _object->_identity.sign(message);
+ }
+ return {Bytes::NONE};
+}
+
+bool Destination::has_link(const Link& link) {
+ assert(_object);
+ return (_object->_links.count(link) > 0);
+}
+
+void Destination::remove_link(const Link& link) {
+ assert(_object);
+ _object->_links.erase(link);
+}

diff --git a/lib/microReticulum/src/Destination.h b/lib/microReticulum/src/Destination.h
new file mode 100755
index 0000000..f741c9c
--- /dev/null
+++ b/lib/microReticulum/src/Destination.h
@@ -0,0 +1,259 @@
+#pragma once
+
+//#include "Reticulum.h"
+//#include "Link.h"
+//#include "Interface.h"
+#include "Identity.h"
+#include "Bytes.h"
+#include "Type.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+#include <map>
+#include <set>
+#include <cassert>
+#include <stdint.h>
+
+namespace RNS {
+
+ class Interface;
+ class Link;
+ class Packet;
+
+ class RequestHandler {
+ public:
+ //p response_generator(path, data, request_id, link_id, remote_identity, requested_at)
+ using response_generator = Bytes(*)(const Bytes& path, const Bytes& data, const Bytes& request_id, const Bytes& link_id, const Identity& remote_identity, double requested_at);
+ public:
+ RequestHandler(const RequestHandler& handler) {
+ _path = handler._path;
+ _response_generator = handler._response_generator;
+ _allow = handler._allow;
+ _allowed_list = handler._allowed_list;
+ }
+ Bytes _path;
+ response_generator _response_generator = nullptr;
+ Type::Destination::request_policies _allow = Type::Destination::ALLOW_NONE;
+ std::set<Bytes> _allowed_list;
+ };
+
+ /**
+ * @brief A class used to describe endpoints in a Reticulum Network. Destination
+ * instances are used both to create outgoing and incoming endpoints. The
+ * destination type will decide if encryption, and what type, is used in
+ * communication with the endpoint. A destination can also announce its
+ * presence on the network, which will also distribute necessary keys for
+ * encrypted communication with it.
+ *
+ * @param identity An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
+ * @param direction ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
+ * @param type ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
+ * @param app_name A string specifying the app name.
+ * @param aspects Any non-zero number of string arguments.
+ */
+ class Destination {
+
+ public:
+ class Callbacks {
+ public:
+ using link_established = void(*)(Link& link);
+ //using packet = void(*)(uint8_t* data, uint16_t data_len, Packet *packet);
+ using packet = void(*)(const Bytes& data, const Packet& packet);
+ using proof_requested = bool(*)(const Packet& packet);
+ public:
+ link_established _link_established = nullptr;
+ packet _packet = nullptr;
+ proof_requested _proof_requested = nullptr;
+ friend class Destination;
+ };
+
+ using PathResponse = std::pair<double, Bytes>;
+
+ public:
+ Destination(Type::NoneConstructor none) {
+ MEM("Destination NONE object created, this: " + std::to_string((uintptr_t)this));
+ }
+ Destination(const Destination& destination) : _object(destination._object) {
+ MEM("Destination object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+ Destination(
+ const Identity& identity,
+ const Type::Destination::directions direction,
+ const Type::Destination::types type,
+ const char* app_name,
+ const char* aspects
+ );
+ Destination(
+ const Identity& identity,
+ const Type::Destination::directions direction,
+ const Type::Destination::types type,
+ const Bytes& hash
+ );
+ virtual ~Destination();
+
+ inline Destination& operator = (const Destination& destination) {
+ _object = destination._object;
+ MEM("Destination object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ return *this;
+ }
+ inline operator bool() const {
+ return _object.get() != nullptr;
+ }
+ inline bool operator < (const Destination& destination) const {
+ return _object.get() < destination._object.get();
+ }
+
+ public:
+ static std::string expand_name(const Identity& identity, const char* app_name, const char* aspects);
+ static Bytes hash(const Identity& identity, const char* app_name, const char* aspects);
+ static Bytes name_hash(const char* app_name, const char* aspects);
+ static std::vector<std::string> app_and_aspects_from_name(const char* full_name);
+ static Bytes hash_from_name_and_identity(const char* full_name, const Identity& identity);
+
+ public:
+ //Packet announce(const Bytes& app_data = {}, bool path_response = false, const Interface& attached_interface = {Type::NONE}, const Bytes& tag = {}, bool send = true);
+ Packet announce(const Bytes& app_data, bool path_response, const Interface& attached_interface, const Bytes& tag = {}, bool send = true);
+ Packet announce(const Bytes& app_data = {}, bool path_response = false);
+
+ /*
+ Set or query whether the destination accepts incoming link requests.
+
+ :param accepts: If ``True`` or ``False``, this method sets whether the destination accepts incoming link requests. If not provided or ``None``, the method returns whether the destination currently accepts link requests.
+ :returns: ``True`` or ``False`` depending on whether the destination accepts incoming link requests, if the *accepts* parameter is not provided or ``None``.
+ */
+ inline void accepts_links(bool accepts) { assert(_object); _object->_accept_link_requests = accepts; }
+ inline bool accepts_links() { assert(_object); return _object->_accept_link_requests; }
+
+ /*
+ Registers a function to be called when a link has been established to
+ this destination.
+
+ :param callback: A function or method with the signature *callback(link)* to be called when a new link is established with this destination.
+ */
+ inline void set_link_established_callback(Callbacks::link_established callback) {
+ assert(_object);
+ _object->_callbacks._link_established = callback;
+ }
+ /*
+ Registers a function to be called when a packet has been received by
+ this destination.
+
+ :param callback: A function or method with the signature *callback(data, packet)* to be called when this destination receives a packet.
+ */
+ inline void set_packet_callback(Callbacks::packet callback) {
+ assert(_object);
+ _object->_callbacks._packet = callback;
+ }
+ /*
+ Registers a function to be called when a proof has been requested for
+ a packet sent to this destination. Allows control over when and if
+ proofs should be returned for received packets.
+
+ :param callback: A function or method to with the signature *callback(packet)* be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.
+ */
+ inline void set_proof_requested_callback(Callbacks::proof_requested callback) {
+ assert(_object);
+ _object->_callbacks._proof_requested = callback;
+ }
+
+ /*
+ Sets the destinations proof strategy.
+
+ :param proof_strategy: One of ``RNS.Destination.PROVE_NONE``, ``RNS.Destination.PROVE_ALL`` or ``RNS.Destination.PROVE_APP``. If ``RNS.Destination.PROVE_APP`` is set, the `proof_requested_callback` will be called to determine whether a proof should be sent or not.
+ */
+ inline void set_proof_strategy(Type::Destination::proof_strategies proof_strategy) {
+ assert(_object);
+ //if (proof_strategy <= PROOF_NONE) {
+ // throw throw std::invalid_argument("Unsupported proof strategy");
+ //}
+ _object->_proof_strategy = proof_strategy;
+ }
+
+ void receive(const Packet& packet);
+ void incoming_link_request(const Bytes& data, const Packet& packet);
+
+ virtual const Bytes encrypt(const Bytes& data);
+ virtual const Bytes decrypt(const Bytes& data);
+ virtual const Bytes sign(const Bytes& message);
+
+ // CBA
+ bool has_link(const Link& link);
+ void remove_link(const Link& link);
+
+ inline std::string toString() const { if (!_object) return ""; return "{Destination:" + _object->_hash.toHex() + "}"; }
+
+ // getters
+ inline Type::Destination::types type() const { assert(_object); return _object->_type; }
+ inline Type::Destination::directions direction() const { assert(_object); return _object->_direction; }
+ inline Type::Destination::proof_strategies proof_strategy() const { assert(_object); return _object->_proof_strategy; }
+ inline const Bytes& hash() const { assert(_object); return _object->_hash; }
+ inline uint16_t mtu() const { assert(_object); return _object->_mtu; }
+ // CBA LINK
+ //inline const Bytes& link_id() const { assert(_object); return _object->_link_id; }
+ //inline Type::Link::status status() const { assert(_object); return _object->_status; }
+ inline const Callbacks& callbacks() const { assert(_object); return _object->_callbacks; }
+ inline const Identity& identity() const { assert(_object); return _object->_identity; }
+ inline const std::map<Bytes, PathResponse>& path_responses() const { assert(_object); return _object->_path_responses; }
+ inline const std::map<Bytes, RequestHandler>& request_handlers() const { assert(_object); return _object->_request_handlers; }
+
+ // setters
+ // CBA Don't allow changing destination hash after construction since it's used as key in collections
+ //inline void hash(const Bytes& hash) { assert(_object); _object->_hash = hash; _object->_hexhash = _object->_hash.toHex(); }
+ inline void type(Type::Destination::types type) { assert(_object); _object->_type = type; }
+ inline void mtu(uint16_t mtu) { assert(_object); _object->_mtu = mtu; }
+ // CBA LINK
+ //inline void link_id(const Bytes& id) { assert(_object); _object->_link_id = id; }
+ //inline void last_outbound(double time) { assert(_object); _object->_last_outbound = time; }
+ //inline void increment_tx() { assert(_object); ++_object->_tx; }
+ //inline void increment_txbytes(uint16_t bytes) { assert(_object); _object->_txbytes += bytes; }
+
+ private:
+ class Object {
+ public:
+ Object(const Identity& identity) : _identity(identity) { MEM("Destination::Data object created, this: " + std::to_string((uintptr_t)this)); }
+ virtual ~Object() { MEM("Destination::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
+ private:
+ bool _accept_link_requests = true;
+ Callbacks _callbacks;
+ std::map<Bytes, RequestHandler> _request_handlers;
+ Type::Destination::types _type;
+ Type::Destination::directions _direction;
+ Type::Destination::proof_strategies _proof_strategy = Type::Destination::PROVE_NONE;
+ uint16_t _mtu = 0;
+
+ std::map<Bytes, PathResponse> _path_responses;
+ std::set<Link> _links;
+
+ Identity _identity;
+ std::string _name;
+
+ // Generate the destination address hash
+ Bytes _hash;
+ Bytes _name_hash;
+ std::string _hexhash;
+
+ // CBA TODO when is _default_app_data a "callable"?
+ Bytes _default_app_data;
+ //z _callback = None
+ //z _proofcallback = None
+
+ // CBA LINK
+ // CBA _link_id is expected by Packet but only present in Link
+ // CBA TODO determine if Link needs to inherit from Destination or vice-versa
+ //Bytes _link_id;
+
+ //Type::Link::status _status;
+
+ //double _last_outbound = 0.0;
+ //uint16_t _tx = 0;
+ //uint32_t _txbytes = 0;
+
+ friend class Destination;
+ };
+ std::shared_ptr<Object> _object;
+
+ };
+
+}
\ No newline at end of file

diff --git a/lib/microReticulum/src/FileStream.h b/lib/microReticulum/src/FileStream.h
new file mode 100755
index 0000000..6b69d65
--- /dev/null
+++ b/lib/microReticulum/src/FileStream.h
@@ -0,0 +1,144 @@
+#pragma once
+
+#include "Utilities/Crc.h"
+#include "Log.h"
+#include "Bytes.h"
+#include "Type.h"
+
+#ifdef ARDUINO
+#include <Stream.h>
+#else
+#include "Utilities/Stream.h"
+#endif
+
+#include <list>
+#include <memory>
+#include <cassert>
+#include <stdint.h>
+
+namespace RNS {
+
+ class FileStreamImpl {
+
+ protected:
+ FileStreamImpl() { MEMF("FileStreamImpl object created, this: 0x%X", this); }
+ public:
+ virtual ~FileStreamImpl() { MEMF("FileStreamImpl object destroyed, this: 0x%X", this); }
+
+ protected:
+ // CBA TODO Can these be pure-virtual???
+ virtual const char* name() = 0;
+ virtual size_t size() = 0;
+ virtual void close() = 0;
+
+ // Print overrides
+ virtual size_t write(uint8_t byte) = 0;
+ virtual size_t write(const uint8_t *buffer, size_t size) = 0;
+
+ // Stream overrides
+ virtual int available() = 0;
+ virtual int read() = 0;
+ virtual int peek() = 0;
+ virtual void flush() = 0;
+
+ friend class FileStream;
+ };
+
+ class FileStream : public Stream {
+
+ public:
+ enum MODE {
+ MODE_READ,
+ MODE_WRITE,
+ MODE_APPEND,
+ };
+
+ public:
+ FileStream(Type::NoneConstructor none) {
+ MEMF("FileStream NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ FileStream(const FileStream& obj) : _impl(obj._impl), _crc(obj._crc) {
+ MEMF("FileStream object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ FileStream(FileStreamImpl* impl) : _impl(impl) {
+ MEMF("FileStream object impl created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ virtual ~FileStream() {
+ MEMF("FileStream object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+
+ inline virtual FileStream& operator = (const FileStream& obj) {
+ _impl = obj._impl;
+ _crc = obj._crc;
+ MEMF("FileStream object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return *this;
+ }
+ inline FileStream& operator = (FileStreamImpl* impl) {
+ _impl.reset(impl);
+ MEMF("FileStream object copy created by impl assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return *this;
+ }
+ inline operator bool() const {
+ MEMF("FileStream object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() != nullptr;
+ }
+ inline bool operator < (const FileStream& obj) const {
+ MEMF("FileStream object <, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() < obj._impl.get();
+ }
+ inline bool operator > (const FileStream& obj) const {
+ MEMF("FileStream object <, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() > obj._impl.get();
+ }
+ inline bool operator == (const FileStream& obj) const {
+ MEMF("FileStream object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() == obj._impl.get();
+ }
+ inline bool operator != (const FileStream& obj) const {
+ MEMF("FileStream object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() != obj._impl.get();
+ }
+ inline FileStreamImpl* get() {
+ return _impl.get();
+ }
+ inline void clear() {
+ _impl.reset();
+ }
+
+ public:
+ inline uint32_t crc() { return _crc; }
+ inline size_t write(const char* str) { return write((const uint8_t*)str, strlen(str)); }
+
+ // File overrides
+ inline const char* name() { assert(_impl); return _impl->name(); }
+ inline size_t size() { assert(_impl); return _impl->size(); }
+ inline void close() { assert(_impl); _impl->close(); }
+
+ // Print overrides
+ inline size_t write(uint8_t byte) { assert(_impl); _crc = Utilities::Crc::crc32(_crc, byte); return _impl->write(byte); }
+ inline size_t write(const uint8_t *buffer, size_t size) { assert(_impl); _crc = Utilities::Crc::crc32(_crc, buffer, size); return _impl->write(buffer, size); }
+
+ // Stream overrides
+ inline int available() { assert(_impl); return _impl->available(); }
+ inline int read() { assert(_impl); if (_impl->available() <= 0) return EOF; int ch = _impl->read(); uint8_t byte = (uint8_t)ch; _crc = Utilities::Crc::crc32(_crc, byte); return ch; }
+ inline int peek() { assert(_impl); return _impl->peek(); }
+ inline void flush() { assert(_impl); _impl->flush(); }
+
+ // getters/setters
+ protected:
+ public:
+
+#ifndef NDEBUG
+ inline std::string debugString() const {
+ std::string dump;
+ dump = "FileStream object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
+ return dump;
+ }
+#endif
+
+ protected:
+ std::shared_ptr<FileStreamImpl> _impl;
+ uint32_t _crc = 0;
+ };
+
+}

diff --git a/lib/microReticulum/src/FileSystem.h b/lib/microReticulum/src/FileSystem.h
new file mode 100755
index 0000000..f224420
--- /dev/null
+++ b/lib/microReticulum/src/FileSystem.h
@@ -0,0 +1,129 @@
+#pragma once
+
+#include "FileStream.h"
+#include "Log.h"
+#include "Bytes.h"
+#include "Type.h"
+
+#include <list>
+#include <memory>
+#include <cassert>
+#include <stdint.h>
+
+namespace RNS {
+
+ class FileSystemImpl {
+
+ protected:
+ FileSystemImpl() { MEMF("FileSystem::FileSystemImpl object created, this: 0x%X", this); }
+ public:
+ virtual ~FileSystemImpl() { MEMF("FileSystem::FileSystemImpl object destroyed, this: 0x%X", this); }
+
+ protected:
+ virtual bool init() { return true; }
+ virtual void loop() {}
+ virtual bool file_exists(const char* file_path) = 0;
+ virtual size_t read_file(const char* file_path, Bytes& data) = 0;
+ virtual size_t write_file(const char* file_path, const Bytes& data) = 0;
+ virtual FileStream open_file(const char* file_path, FileStream::MODE file_mode) = 0;
+ virtual bool remove_file(const char* file_path) = 0;
+ virtual bool rename_file(const char* from_file_path, const char* to_file_path) = 0;
+ virtual bool directory_exists(const char* directory_path) = 0;
+ virtual bool create_directory(const char* directory_path) = 0;
+ virtual bool remove_directory(const char* directory_path) = 0;
+ virtual std::list<std::string> list_directory(const char* directory_path) = 0;
+ virtual size_t storage_size() = 0;
+ virtual size_t storage_available() = 0;
+
+ friend class FileSystem;
+ };
+
+ class FileSystem {
+
+ public:
+ FileSystem(Type::NoneConstructor none) {
+ MEMF("FileSystem NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ FileSystem(const FileSystem& obj) : _impl(obj._impl) {
+ MEMF("FileSystem object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ FileSystem(FileSystemImpl* impl) : _impl(impl) {
+ MEMF("FileSystem object impl created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ virtual ~FileSystem() {
+ MEMF("FileSystem object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+
+ inline FileSystem& operator = (const FileSystem& obj) {
+ _impl = obj._impl;
+ MEMF("FileSystem object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return *this;
+ }
+ inline FileSystem& operator = (FileSystemImpl* impl) {
+ _impl.reset(impl);
+ MEMF("FileSystem object copy created by impl assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return *this;
+ }
+ inline operator bool() const {
+ MEMF("FileSystem object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() != nullptr;
+ }
+ inline bool operator < (const FileSystem& obj) const {
+ MEMF("FileSystem object <, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() < obj._impl.get();
+ }
+ inline bool operator > (const FileSystem& obj) const {
+ MEMF("FileSystem object <, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() > obj._impl.get();
+ }
+ inline bool operator == (const FileSystem& obj) const {
+ MEMF("FileSystem object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() == obj._impl.get();
+ }
+ inline bool operator != (const FileSystem& obj) const {
+ MEMF("FileSystem object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() != obj._impl.get();
+ }
+ inline FileSystemImpl* get() {
+ return _impl.get();
+ }
+ inline void clear() {
+ _impl.reset();
+ }
+
+ public:
+ inline bool init() { assert(_impl); return _impl->init(); }
+ inline void loop() { assert(_impl); return _impl->loop(); }
+ inline bool file_exists(const char* file_path) { assert(_impl); return _impl->file_exists(file_path); }
+ inline size_t read_file(const char* file_path, Bytes& data) { assert(_impl); return _impl->read_file(file_path, data); }
+ inline size_t write_file(const char* file_path, const Bytes& data) { assert(_impl); return _impl->write_file(file_path, data); }
+ inline FileStream open_file(const char* file_path, FileStream::MODE file_mode) { return _impl->open_file(file_path, file_mode); }
+ inline bool remove_file(const char* file_path) { assert(_impl); return _impl->remove_file(file_path); }
+ inline bool rename_file(const char* from_file_path, const char* to_file_path) { assert(_impl); return _impl->rename_file(from_file_path, to_file_path); }
+ inline bool directory_exists(const char* directory_path) { assert(_impl); return _impl->directory_exists(directory_path); }
+ inline bool create_directory(const char* directory_path) { assert(_impl); return _impl->create_directory(directory_path); }
+ inline bool remove_directory(const char* directory_path) { assert(_impl); return _impl->remove_directory(directory_path); }
+ inline std::list<std::string> list_directory(const char* directory_path) { assert(_impl); return _impl->list_directory(directory_path); }
+ inline size_t storage_size() { assert(_impl); return _impl->storage_size(); }
+ inline size_t storage_available() { assert(_impl); return _impl->storage_available(); }
+
+ private:
+ std::list<std::string> _empty;
+
+ // getters/setters
+ protected:
+ public:
+
+#ifndef NDEBUG
+ inline std::string debugString() const {
+ std::string dump;
+ dump = "FileSystem object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
+ return dump;
+ }
+#endif
+
+ protected:
+ std::shared_ptr<FileSystemImpl> _impl;
+ };
+
+}

diff --git a/lib/microReticulum/src/Identity.cpp b/lib/microReticulum/src/Identity.cpp
new file mode 100755
index 0000000..0619683
--- /dev/null
+++ b/lib/microReticulum/src/Identity.cpp
@@ -0,0 +1,661 @@
+#include "Identity.h"
+
+#include "Reticulum.h"
+#include "Transport.h"
+#include "Packet.h"
+#include "Log.h"
+#include "Utilities/OS.h"
+#include "Cryptography/Ed25519.h"
+#include "Cryptography/X25519.h"
+#include "Cryptography/HKDF.h"
+#include "Cryptography/Token.h"
+#include "Cryptography/Random.h"
+
+#include <algorithm>
+#include <string.h>
+
+using namespace RNS;
+using namespace RNS::Type::Identity;
+using namespace RNS::Cryptography;
+using namespace RNS::Utilities;
+
+/*static*/ std::map<Bytes, Identity::IdentityEntry> Identity::_known_destinations;
+/*static*/ bool Identity::_saving_known_destinations = false;
+// CBA
+// CBA ACCUMULATES
+/*static*/ //uint16_t Identity::_known_destinations_maxsize = 100;
+/*static*/ uint16_t Identity::_known_destinations_maxsize = 100;
+
+Identity::Identity(bool create_keys /*= true*/) : _object(new Object()) {
+ if (create_keys) {
+ createKeys();
+ }
+ MEM("Identity object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+}
+
+void Identity::createKeys() {
+ assert(_object);
+
+ // CRYPTO: create encryption private keys
+ _object->_prv = Cryptography::X25519PrivateKey::generate();
+ _object->_prv_bytes = _object->_prv->private_bytes();
+ //TRACE("Identity::createKeys: prv bytes: " + _object->_prv_bytes.toHex());
+
+ // CRYPTO: create signature private keys
+ _object->_sig_prv = Cryptography::Ed25519PrivateKey::generate();
+ _object->_sig_prv_bytes = _object->_sig_prv->private_bytes();
+ //TRACE("Identity::createKeys: sig prv bytes: " + _object->_sig_prv_bytes.toHex());
+
+ // CRYPTO: create encryption public keys
+ _object->_pub = _object->_prv->public_key();
+ _object->_pub_bytes = _object->_pub->public_bytes();
+ //TRACE("Identity::createKeys: pub bytes: " + _object->_pub_bytes.toHex());
+
+ // CRYPTO: create signature public keys
+ _object->_sig_pub = _object->_sig_prv->public_key();
+ _object->_sig_pub_bytes = _object->_sig_pub->public_bytes();
+ //TRACE("Identity::createKeys: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
+
+ update_hashes();
+
+ VERBOSE("Identity keys created for " + _object->_hash.toHex());
+}
+
+/*
+Load a private key into the instance.
+
+:param prv_bytes: The private key as *bytes*.
+:returns: True if the key was loaded, otherwise False.
+*/
+bool Identity::load_private_key(const Bytes& prv_bytes) {
+ assert(_object);
+
+ try {
+
+ //p self.prv_bytes = prv_bytes[:Identity.KEYSIZE//8//2]
+ _object->_prv_bytes = prv_bytes.left(Type::Identity::KEYSIZE/8/2);
+ _object->_prv = X25519PrivateKey::from_private_bytes(_object->_prv_bytes);
+ //TRACE("Identity::load_private_key: prv bytes: " + _object->_prv_bytes.toHex());
+
+ //p self.sig_prv_bytes = prv_bytes[Identity.KEYSIZE//8//2:]
+ _object->_sig_prv_bytes = prv_bytes.mid(Type::Identity::KEYSIZE/8/2);
+ _object->_sig_prv = Ed25519PrivateKey::from_private_bytes(_object->_sig_prv_bytes);
+ //TRACE("Identity::load_private_key: sig prv bytes: " + _object->_sig_prv_bytes.toHex());
+
+ _object->_pub = _object->_prv->public_key();
+ _object->_pub_bytes = _object->_pub->public_bytes();
+ //TRACE("Identity::load_private_key: pub bytes: " + _object->_pub_bytes.toHex());
+
+ _object->_sig_pub = _object->_sig_prv->public_key();
+ _object->_sig_pub_bytes = _object->_sig_pub->public_bytes();
+ //TRACE("Identity::load_private_key: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
+
+ update_hashes();
+
+ return true;
+ }
+ catch (std::exception& e) {
+ //p raise e
+ ERROR("Failed to load identity key");
+ ERRORF("The contained exception was: %s", e.what());
+ return false;
+ }
+}
+
+/*
+Load a public key into the instance.
+
+:param pub_bytes: The public key as *bytes*.
+:returns: True if the key was loaded, otherwise False.
+*/
+void Identity::load_public_key(const Bytes& pub_bytes) {
+ assert(_object);
+
+ try {
+
+ //_pub_bytes = pub_bytes[:Identity.KEYSIZE//8//2]
+ _object->_pub_bytes = pub_bytes.left(Type::Identity::KEYSIZE/8/2);
+ //TRACE("Identity::load_public_key: pub bytes: " + _object->_pub_bytes.toHex());
+
+ //_sig_pub_bytes = pub_bytes[Identity.KEYSIZE//8//2:]
+ _object->_sig_pub_bytes = pub_bytes.mid(Type::Identity::KEYSIZE/8/2);
+ //TRACE("Identity::load_public_key: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
+
+ _object->_pub = X25519PublicKey::from_public_bytes(_object->_pub_bytes);
+ _object->_sig_pub = Ed25519PublicKey::from_public_bytes(_object->_sig_pub_bytes);
+
+ update_hashes();
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while loading public key, the contained exception was: %s", e.what());
+ }
+}
+
+bool Identity::load(const char* path) {
+ TRACE("Reading identity key from storage...");
+#if defined(RNS_USE_FS)
+ try {
+ Bytes prv_bytes;
+ if (OS::read_file(path, prv_bytes) > 0) {
+ return load_private_key(prv_bytes);
+ }
+ else {
+ return false;
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error while loading identity from " + std::string(path));
+ ERRORF("The contained exception was: %s", e.what());
+ }
+#endif
+ return false;
+}
+
+/*
+Saves the identity to a file. This will write the private key to disk,
+and anyone with access to this file will be able to decrypt all
+communication for the identity. Be very careful with this method.
+
+:param path: The full path specifying where to save the identity.
+:returns: True if the file was saved, otherwise False.
+*/
+bool Identity::to_file(const char* path) {
+ TRACE("Writing identity key to storage...");
+#if defined(RNS_USE_FS)
+ try {
+ return (OS::write_file(path, get_private_key()) == get_private_key().size());
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while saving identity to %s", path);
+ ERRORF("The contained exception was: %s", e.what());
+ }
+#endif
+ return false;
+}
+
+
+/*
+Create a new :ref:`RNS.Identity<api-identity>` instance from a file.
+Can be used to load previously created and saved identities into Reticulum.
+
+:param path: The full path to the saved :ref:`RNS.Identity<api-identity>` data
+:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the loaded data was invalid.
+*/
+/*static*/ const Identity Identity::from_file(const char* path) {
+ Identity identity(false);
+ if (identity.load(path)) {
+ return identity;
+ }
+ return {Type::NONE};
+}
+
+/*static*/ void Identity::remember(const Bytes& packet_hash, const Bytes& destination_hash, const Bytes& public_key, const Bytes& app_data /*= {Bytes::NONE}*/) {
+ if (public_key.size() != Type::Identity::KEYSIZE/8) {
+ throw std::invalid_argument("Can't remember " + destination_hash.toHex() + ", the public key size of " + std::to_string(public_key.size()) + " is not valid.");
+ }
+ else {
+ //p _known_destinations[destination_hash] = {OS::time(), packet_hash, public_key, app_data};
+ // CBA ACCUMULATES
+ _known_destinations.insert({destination_hash, {OS::time(), packet_hash, public_key, app_data}});
+ }
+}
+
+/*
+Recall identity for a destination hash.
+
+:param destination_hash: Destination hash as *bytes*.
+:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
+*/
+/*static*/ Identity Identity::recall(const Bytes& destination_hash) {
+ TRACE("Identity::recall...");
+ auto iter = _known_destinations.find(destination_hash);
+ if (iter != _known_destinations.end()) {
+ TRACE("Identity::recall: Found identity entry for destination " + destination_hash.toHex());
+ const IdentityEntry& identity_data = (*iter).second;
+ Identity identity(false);
+ identity.load_public_key(identity_data._public_key);
+ identity.app_data(identity_data._app_data);
+ return identity;
+ }
+ else {
+ TRACE("Identity::recall: Unable to find identity entry for destination " + destination_hash.toHex() + ", performing destination lookup...");
+ Destination registered_destination(Transport::find_destination_from_hash(destination_hash));
+ if (registered_destination) {
+ TRACE("Identity::recall: Found destination " + destination_hash.toHex());
+ Identity identity(false);
+ identity.load_public_key(registered_destination.identity().get_public_key());
+ identity.app_data({Bytes::NONE});
+ return identity;
+ }
+ TRACE("Identity::recall: Unable to find destination " + destination_hash.toHex());
+ return {Type::NONE};
+ }
+}
+
+/*
+Recall last heard app_data for a destination hash.
+
+:param destination_hash: Destination hash as *bytes*.
+:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
+*/
+/*static*/ Bytes Identity::recall_app_data(const Bytes& destination_hash) {
+ TRACE("Identity::recall_app_data...");
+ auto iter = _known_destinations.find(destination_hash);
+ if (iter != _known_destinations.end()) {
+ TRACE("Identity::recall_app_data: Found identity entry for destination " + destination_hash.toHex());
+ const IdentityEntry& identity_data = (*iter).second;
+ return identity_data._app_data;
+ }
+ else {
+ TRACE("Identity::recall_app_data: Unable to find identity entry for destination " + destination_hash.toHex());
+ return {Bytes::NONE};
+ }
+}
+
+/*static*/ bool Identity::save_known_destinations() {
+ // TODO: Improve the storage method so we don't have to
+ // deserialize and serialize the entire table on every
+ // save, but the only changes. It might be possible to
+ // simply overwrite on exit now that every local client
+ // disconnect triggers a data persist.
+
+ bool success = false;
+ try {
+ if (_saving_known_destinations) {
+ double wait_interval = 0.2;
+ double wait_timeout = 5;
+ double wait_start = OS::time();
+ while (_saving_known_destinations) {
+ OS::sleep(wait_interval);
+ if (OS::time() > (wait_start + wait_timeout)) {
+ ERROR("Could not save known destinations to storage, waiting for previous save operation timed out.");
+ return false;
+ }
+ }
+ }
+
+ _saving_known_destinations = true;
+ double save_start = OS::time();
+
+ std::map<Bytes, IdentityEntry> storage_known_destinations;
+// TODO
+/*
+ if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
+ try:
+ file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
+ storage_known_destinations = umsgpack.load(file)
+ file.close()
+ except:
+ pass
+*/
+
+ for (auto& [destination_hash, identity_entry] : storage_known_destinations) {
+ if (_known_destinations.find(destination_hash) == _known_destinations.end()) {
+ //_known_destinations[destination_hash] = storage_known_destinations[destination_hash];
+ //_known_destinations[destination_hash] = identity_entry;
+ // CBA ACCUMULATES
+ _known_destinations.insert({destination_hash, identity_entry});
+ }
+ }
+
+// TODO
+/*
+ DEBUG("Saving " + std::to_string(_known_destinations.size()) + " known destinations to storage...");
+ file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
+ umsgpack.dump(Identity.known_destinations, file)
+ file.close()
+*/
+
+ std::string time_str;
+ double save_time = OS::time() - save_start;
+ if (save_time < 1) {
+ time_str = std::to_string((int)(save_time*1000)) + " ms";
+ }
+ else {
+ time_str = std::to_string(OS::round(save_time, 1)) + " s";
+ }
+
+ DEBUG("Saved known destinations to storage in " + time_str);
+
+ success = true;
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while saving known destinations to disk, the contained exception was: %s", e.what());
+ }
+
+ _saving_known_destinations = false;
+
+ return success;
+}
+
+/*static*/ void Identity::load_known_destinations() {
+// TODO
+/*
+ if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
+ try:
+ file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
+ loaded_known_destinations = umsgpack.load(file)
+ file.close()
+
+ Identity.known_destinations = {}
+ for known_destination in loaded_known_destinations:
+ if len(known_destination) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
+ Identity.known_destinations[known_destination] = loaded_known_destinations[known_destination]
+
+ RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
+ except:
+ RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
+ else:
+ RNS.log("Destinations file does not exist, no known destinations loaded", RNS.LOG_VERBOSE)
+*/
+
+}
+
+/*static*/ void Identity::cull_known_destinations() {
+ TRACE("Transport::cull_path_table()");
+ if (_known_destinations.size() > _known_destinations_maxsize) {
+ // prune by age
+ uint16_t count = 0;
+ std::vector<std::pair<Bytes, IdentityEntry>> sorted_pairs;
+ // Copy key/value pairs from map into vector
+ std::for_each(_known_destinations.begin(), _known_destinations.end(), [&](const std::pair<const Bytes, IdentityEntry>& ref) {
+ sorted_pairs.push_back(ref);
+ });
+ // Sort vector using specified comparator
+ std::sort(sorted_pairs.begin(), sorted_pairs.end(), [](const std::pair<Bytes, IdentityEntry> &left, const std::pair<Bytes, IdentityEntry> &right) {
+ return left.second._timestamp < right.second._timestamp;
+ });
+ // Iterate vector of sorted values
+ for (auto& [destination_hash, identity_entry] : sorted_pairs) {
+ TRACE("Transport::cull_path_table: Removing destination " + destination_hash.toHex() + " from known destinations");
+ // Remove destination from known destinations
+ if (_known_destinations.erase(destination_hash) < 1) {
+ WARNING("Failed to remove destination " + destination_hash.toHex() + " from known destinations");
+ }
+ ++count;
+ if (_known_destinations.size() <= _known_destinations_maxsize) {
+ break;
+ }
+ }
+ DEBUG("Removed " + std::to_string(count) + " path(s) from known destinations");
+ }
+}
+
+/*static*/ bool Identity::validate_announce(const Packet& packet) {
+ try {
+ if (packet.packet_type() == Type::Packet::ANNOUNCE) {
+ Bytes destination_hash = packet.destination_hash();
+ //TRACE("Identity::validate_announce: destination_hash: " + packet.destination_hash().toHex());
+ Bytes public_key = packet.data().left(KEYSIZE/8);
+ //TRACE("Identity::validate_announce: public_key: " + public_key.toHex());
+ Bytes name_hash = packet.data().mid(KEYSIZE/8, NAME_HASH_LENGTH/8);
+ //TRACE("Identity::validate_announce: name_hash: " + name_hash.toHex());
+ Bytes random_hash = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8, RANDOM_HASH_LENGTH/8);
+ //TRACE("Identity::validate_announce: random_hash: " + random_hash.toHex());
+ Bytes signature = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8, SIGLENGTH/8);
+ //TRACE("Identity::validate_announce: signature: " + signature.toHex());
+ Bytes app_data;
+ if (packet.data().size() > (KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8)) {
+ app_data = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8);
+ }
+ //TRACE("Identity::validate_announce: app_data: " + app_data.toHex());
+ //TRACE("Identity::validate_announce: app_data text: " + app_data.toString());
+
+ Bytes signed_data;
+ signed_data << packet.destination_hash() << public_key << name_hash << random_hash+app_data;
+ //TRACE("Identity::validate_announce: signed_data: " + signed_data.toHex());
+
+ if (packet.data().size() <= KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8) {
+ app_data.clear();
+ }
+
+ Identity announced_identity(false);
+ announced_identity.load_public_key(public_key);
+
+ if (announced_identity.pub() && announced_identity.validate(signature, signed_data)) {
+ Bytes hash_material = name_hash << announced_identity.hash();
+ Bytes expected_hash = full_hash(hash_material).left(Type::Reticulum::TRUNCATED_HASHLENGTH/8);
+ //TRACE("Identity::validate_announce: destination_hash: " + packet.destination_hash().toHex());
+ //TRACE("Identity::validate_announce: expected_hash: " + expected_hash.toHex());
+
+ if (packet.destination_hash() == expected_hash) {
+ // Check if we already have a public key for this destination
+ // and make sure the public key is not different.
+ auto iter = _known_destinations.find(packet.destination_hash());
+ if (iter != _known_destinations.end()) {
+ IdentityEntry& identity_entry = (*iter).second;
+ if (public_key != identity_entry._public_key) {
+ // In reality, this should never occur, but in the odd case
+ // that someone manages a hash collision, we reject the announce.
+ CRITICAL("Received announce with valid signature and destination hash, but announced public key does not match already known public key.");
+ CRITICAL("This may indicate an attempt to modify network paths, or a random hash collision. The announce was rejected.");
+ return false;
+ }
+ }
+
+ remember(packet.get_hash(), packet.destination_hash(), public_key, app_data);
+ //p del announced_identity
+
+ std::string signal_str;
+// TODO
+/*
+ if packet.rssi != None or packet.snr != None:
+ signal_str = " ["
+ if packet.rssi != None:
+ signal_str += "RSSI "+str(packet.rssi)+"dBm"
+ if packet.snr != None:
+ signal_str += ", "
+ if packet.snr != None:
+ signal_str += "SNR "+str(packet.snr)+"dB"
+ signal_str += "]"
+ else:
+ signal_str = ""
+*/
+
+ if (packet.transport_id()) {
+ TRACE("Valid announce for " + packet.destination_hash().toHex() + " " + std::to_string(packet.hops()) + " hops away, received via " + packet.transport_id().toHex() + " on " + packet.receiving_interface().toString() + signal_str);
+ }
+ else {
+ TRACE("Valid announce for " + packet.destination_hash().toHex() + " " + std::to_string(packet.hops()) + " hops away, received on " + packet.receiving_interface().toString() + signal_str);
+ }
+
+ return true;
+ }
+ else {
+ DEBUG("Received invalid announce for " + packet.destination_hash().toHex() + ": Destination mismatch.");
+ return false;
+ }
+ }
+ else {
+ DEBUG("Received invalid announce for " + packet.destination_hash().toHex() + ": Invalid signature.");
+ //p del announced_identity
+ return false;
+ }
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error occurred while validating announce. The contained exception was: " + std::string(e.what()));
+ return false;
+ }
+ return false;
+}
+
+/*static*/ void Identity::persist_data() {
+ if (!Transport::reticulum() || !Transport::reticulum().is_connected_to_shared_instance()) {
+ save_known_destinations();
+ }
+}
+
+/*static*/ void Identity::exit_handler() {
+ persist_data();
+}
+
+/*
+Encrypts information for the identity.
+
+:param plaintext: The plaintext to be encrypted as *bytes*.
+:returns: Ciphertext token as *bytes*.
+:raises: *KeyError* if the instance does not hold a public key.
+*/
+const Bytes Identity::encrypt(const Bytes& plaintext) const {
+ assert(_object);
+ TRACE("Identity::encrypt: encrypting data...");
+ if (!_object->_pub) {
+ throw std::runtime_error("Encryption failed because identity does not hold a public key");
+ }
+ Cryptography::X25519PrivateKey::Ptr ephemeral_key = Cryptography::X25519PrivateKey::generate();
+ Bytes ephemeral_pub_bytes = ephemeral_key->public_key()->public_bytes();
+ TRACE("Identity::encrypt: ephemeral public key: " + ephemeral_pub_bytes.toHex());
+
+ // CRYPTO: create shared key for key exchange using own public key
+ //shared_key = ephemeral_key.exchange(self.pub)
+ Bytes shared_key = ephemeral_key->exchange(_object->_pub_bytes);
+ TRACE("Identity::encrypt: shared key: " + shared_key.toHex());
+
+ Bytes derived_key = Cryptography::hkdf(
+ DERIVED_KEY_LENGTH,
+ shared_key,
+ get_salt(),
+ get_context()
+ );
+ TRACE("Identity::encrypt: derived key: " + derived_key.toHex());
+
+ Cryptography::Token token(derived_key);
+ TRACE("Identity::encrypt: Token encrypting data of length " + std::to_string(plaintext.size()));
+ TRACE("Identity::encrypt: plaintext: " + plaintext.toHex());
+ Bytes ciphertext = token.encrypt(plaintext);
+ TRACE("Identity::encrypt: ciphertext: " + ciphertext.toHex());
+
+ return ephemeral_pub_bytes + ciphertext;
+}
+
+
+/*
+Decrypts information for the identity.
+
+:param ciphertext: The ciphertext to be decrypted as *bytes*.
+:returns: Plaintext as *bytes*, or *None* if decryption fails.
+:raises: *KeyError* if the instance does not hold a private key.
+*/
+const Bytes Identity::decrypt(const Bytes& ciphertext_token) const {
+ assert(_object);
+ TRACE("Identity::decrypt: decrypting data...");
+ if (!_object->_prv) {
+ throw std::runtime_error("Decryption failed because identity does not hold a private key");
+ }
+ if (ciphertext_token.size() <= Type::Identity::KEYSIZE/8/2) {
+ DEBUG("Decryption failed because the token size " + std::to_string(ciphertext_token.size()) + " was invalid.");
+ return {Bytes::NONE};
+ }
+ Bytes plaintext;
+ try {
+ //peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
+ Bytes peer_pub_bytes = ciphertext_token.left(Type::Identity::KEYSIZE/8/2);
+ //peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
+ //Cryptography::X25519PublicKey::Ptr peer_pub = Cryptography::X25519PublicKey::from_public_bytes(peer_pub_bytes);
+ TRACE("Identity::decrypt: peer public key: " + peer_pub_bytes.toHex());
+
+ // CRYPTO: create shared key for key exchange using peer public key
+ //shared_key = _object->_prv->exchange(peer_pub);
+ Bytes shared_key = _object->_prv->exchange(peer_pub_bytes);
+ TRACE("Identity::decrypt: shared key: " + shared_key.toHex());
+
+ Bytes derived_key = Cryptography::hkdf(
+ DERIVED_KEY_LENGTH,
+ shared_key,
+ get_salt(),
+ get_context()
+ );
+ TRACE("Identity::decrypt: derived key: " + derived_key.toHex());
+
+ Cryptography::Token token(derived_key);
+ //ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
+ Bytes ciphertext(ciphertext_token.mid(Type::Identity::KEYSIZE/8/2));
+ TRACE("Identity::decrypt: Token decrypting data of length " + std::to_string(ciphertext.size()));
+ TRACE("Identity::decrypt: ciphertext: " + ciphertext.toHex());
+ plaintext = token.decrypt(ciphertext);
+ TRACE("Identity::decrypt: plaintext: " + plaintext.toHex());
+ //TRACE("Identity::decrypt: Token decrypted data of length " + std::to_string(plaintext.size()));
+ }
+ catch (std::exception& e) {
+ DEBUG("Decryption by " + toString() + " failed: " + e.what());
+ }
+
+ return plaintext;
+}
+
+/*
+Signs information by the identity.
+
+:param message: The message to be signed as *bytes*.
+:returns: Signature as *bytes*.
+:raises: *KeyError* if the instance does not hold a private key.
+*/
+const Bytes Identity::sign(const Bytes& message) const {
+ assert(_object);
+ if (!_object->_sig_prv) {
+ throw std::runtime_error("Signing failed because identity does not hold a private key");
+ }
+ try {
+ return _object->_sig_prv->sign(message);
+ }
+ catch (std::exception& e) {
+ ERROR("The identity " + toString() + " could not sign the requested message. The contained exception was: " + e.what());
+ throw e;
+ }
+}
+
+/*
+Validates the signature of a signed message.
+
+:param signature: The signature to be validated as *bytes*.
+:param message: The message to be validated as *bytes*.
+:returns: True if the signature is valid, otherwise False.
+:raises: *KeyError* if the instance does not hold a public key.
+*/
+bool Identity::validate(const Bytes& signature, const Bytes& message) const {
+ assert(_object);
+ if (_object->_pub) {
+ try {
+ TRACE("Identity::validate: Attempting to verify signature: " + signature.toHex() + " and message: " + message.toHex());
+ _object->_sig_pub->verify(signature, message);
+ return true;
+ }
+ catch (std::exception& e) {
+ return false;
+ }
+ }
+ else {
+ throw std::runtime_error("Signature validation failed because identity does not hold a public key");
+ }
+}
+
+void Identity::prove(const Packet& packet, const Destination& destination /*= {Type::NONE}*/) const {
+ assert(_object);
+ Bytes signature(sign(packet.packet_hash()));
+ Bytes proof_data;
+ if (RNS::Reticulum::should_use_implicit_proof()) {
+ proof_data = signature;
+ TRACE("Identity::prove: implicit proof data: " + proof_data.toHex());
+ }
+ else {
+ proof_data = packet.packet_hash() + signature;
+ TRACE("Identity::prove: explicit proof data: " + proof_data.toHex());
+ }
+
+ if (!destination) {
+ TRACE("Identity::prove: proving packet with proof destination...");
+ ProofDestination proof_destination = packet.generate_proof_destination();
+ Packet proof(proof_destination, packet.receiving_interface(), proof_data, Type::Packet::PROOF);
+ proof.send();
+ }
+ else {
+ TRACE("Identity::prove: proving packet with specified destination...");
+ Packet proof(destination, packet.receiving_interface(), proof_data, Type::Packet::PROOF);
+ proof.send();
+ }
+}
+
+void Identity::prove(const Packet& packet) const {
+ prove(packet, {Type::NONE});
+}

diff --git a/lib/microReticulum/src/Identity.h b/lib/microReticulum/src/Identity.h
new file mode 100755
index 0000000..0459aa1
--- /dev/null
+++ b/lib/microReticulum/src/Identity.h
@@ -0,0 +1,200 @@
+#pragma once
+
+#include "Log.h"
+#include "Bytes.h"
+#include "Type.h"
+#include "Cryptography/Hashes.h"
+#include "Cryptography/Ed25519.h"
+#include "Cryptography/X25519.h"
+#include "Cryptography/Token.h"
+
+#include <map>
+#include <string>
+#include <memory>
+#include <cassert>
+
+namespace RNS {
+
+ class Destination;
+ class Packet;
+
+ class Identity {
+
+ private:
+ class IdentityEntry {
+ public:
+ IdentityEntry(double timestamp, const Bytes& packet_hash, const Bytes& public_key, const Bytes& app_data) :
+ _timestamp(timestamp),
+ _packet_hash(packet_hash),
+ _public_key(public_key),
+ _app_data(app_data)
+ {
+ }
+ public:
+ double _timestamp = 0;
+ Bytes _packet_hash;
+ Bytes _public_key;
+ Bytes _app_data;
+ };
+
+ public:
+ static std::map<Bytes, IdentityEntry> _known_destinations;
+ static bool _saving_known_destinations;
+ // CBA
+ static uint16_t _known_destinations_maxsize;
+
+ public:
+ Identity(bool create_keys = true);
+ Identity(Type::NoneConstructor none) {
+ MEM("Identity NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+ Identity(const Identity& identity) : _object(identity._object) {
+ MEM("Identity object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+ virtual ~Identity() {
+ MEM("Identity object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+
+ inline Identity& operator = (const Identity& identity) {
+ _object = identity._object;
+ MEM("Identity object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ return *this;
+ }
+ inline operator bool() const {
+ return _object.get() != nullptr;
+ }
+ inline bool operator < (const Identity& identity) const {
+ return _object.get() < identity._object.get();
+ }
+
+ public:
+ void createKeys();
+
+ /*
+ :returns: The private key as *bytes*
+ */
+ inline const Bytes get_private_key() const {
+ assert(_object);
+ return _object->_prv_bytes + _object->_sig_prv_bytes;
+ }
+ /*
+ :returns: The public key as *bytes*
+ */
+ inline const Bytes get_public_key() const {
+ assert(_object);
+ return _object->_pub_bytes + _object->_sig_pub_bytes;
+ }
+ bool load_private_key(const Bytes& prv_bytes);
+ void load_public_key(const Bytes& pub_bytes);
+ inline void update_hashes() {
+ assert(_object);
+ _object->_hash = truncated_hash(get_public_key());
+ TRACE("Identity::update_hashes: hash: " + _object->_hash.toHex());
+ _object->_hexhash = _object->_hash.toHex();
+ };
+ bool load(const char* path);
+ bool to_file(const char* path);
+
+ inline const Bytes& get_salt() const { assert(_object); return _object->_hash; }
+ inline const Bytes get_context() const { return {Bytes::NONE}; }
+
+ const Bytes encrypt(const Bytes& plaintext) const;
+ const Bytes decrypt(const Bytes& ciphertext_token) const;
+ const Bytes sign(const Bytes& message) const;
+ bool validate(const Bytes& signature, const Bytes& message) const;
+ // CBA following default for reference value requires inclusiion of header
+ //void prove(const Packet& packet, const Destination& destination = {Type::NONE}) const;
+ void prove(const Packet& packet, const Destination& destination) const;
+ void prove(const Packet& packet) const;
+
+ static const Identity from_file(const char* path);
+ static void remember(const Bytes& packet_hash, const Bytes& destination_hash, const Bytes& public_key, const Bytes& app_data = {Bytes::NONE});
+ static Identity recall(const Bytes& destination_hash);
+ static Bytes recall_app_data(const Bytes& destination_hash);
+ static bool save_known_destinations();
+ static void load_known_destinations();
+ // CBA
+ static void cull_known_destinations();
+
+ /*
+ Get a SHA-256 hash of passed data.
+
+ :param data: Data to be hashed as *bytes*.
+ :returns: SHA-256 hash as *bytes*
+ */
+ static inline const Bytes full_hash(const Bytes& data) {
+ return Cryptography::sha256(data);
+ }
+
+ /*
+ Get a truncated SHA-256 hash of passed data.
+
+ :param data: Data to be hashed as *bytes*.
+ :returns: Truncated SHA-256 hash as *bytes*
+ */
+ static inline const Bytes truncated_hash(const Bytes& data) {
+ //p return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
+ return full_hash(data).left(Type::Identity::TRUNCATED_HASHLENGTH/8);
+ }
+
+ /*
+ Get a random SHA-256 hash.
+
+ :param data: Data to be hashed as *bytes*.
+ :returns: Truncated SHA-256 hash of random data as *bytes*
+ */
+ static inline const Bytes get_random_hash() {
+ return truncated_hash(Cryptography::random(Type::Identity::TRUNCATED_HASHLENGTH/8));
+ }
+
+ static bool validate_announce(const Packet& packet);
+ static void persist_data();
+ static void exit_handler();
+
+ // getters/setters
+ inline const Bytes& encryptionPrivateKey() const { assert(_object); return _object->_prv_bytes; }
+ inline const Bytes& signingPrivateKey() const { assert(_object); return _object->_sig_prv_bytes; }
+ inline const Bytes& encryptionPublicKey() const { assert(_object); return _object->_pub_bytes; }
+ inline const Bytes& signingPublicKey() const { assert(_object); return _object->_sig_pub_bytes; }
+ inline const Bytes& hash() const { assert(_object); return _object->_hash; }
+ inline std::string hexhash() const { assert(_object); return _object->_hexhash; }
+ inline const Bytes& app_data() const { assert(_object); return _object->_app_data; }
+ inline void app_data(const Bytes& app_data) { assert(_object); _object->_app_data = app_data; }
+ inline const Cryptography::X25519PrivateKey::Ptr prv() const { assert(_object); return _object->_prv; }
+ inline const Cryptography::Ed25519PrivateKey::Ptr sig_prv() const { assert(_object); return _object->_sig_prv; }
+ inline const Cryptography::X25519PublicKey::Ptr pub() const { assert(_object); return _object->_pub; }
+ inline const Cryptography::Ed25519PublicKey::Ptr sig_pub() const { assert(_object); return _object->_sig_pub; }
+
+ inline std::string toString() const { if (!_object) return ""; return "{Identity:" + _object->_hash.toHex() + "}"; }
+
+ private:
+ class Object {
+ public:
+ Object() { MEM("Identity::Data object created, this: " + std::to_string((uintptr_t)this)); }
+ virtual ~Object() { MEM("Identity::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
+ private:
+
+ Cryptography::X25519PrivateKey::Ptr _prv;
+ Bytes _prv_bytes;
+
+ Cryptography::Ed25519PrivateKey::Ptr _sig_prv;
+ Bytes _sig_prv_bytes;
+
+ Cryptography::X25519PublicKey::Ptr _pub;
+ Bytes _pub_bytes;
+
+ Cryptography::Ed25519PublicKey::Ptr _sig_pub;
+ Bytes _sig_pub_bytes;
+
+ Bytes _hash;
+ std::string _hexhash;
+
+ Bytes _app_data;
+
+ friend class Identity;
+ };
+ std::shared_ptr<Object> _object;
+
+ };
+
+}
\ No newline at end of file

diff --git a/lib/microReticulum/src/Interface.cpp b/lib/microReticulum/src/Interface.cpp
new file mode 100755
index 0000000..18bde30
--- /dev/null
+++ b/lib/microReticulum/src/Interface.cpp
@@ -0,0 +1,104 @@
+#include "Interface.h"
+
+#include "Identity.h"
+#include "Transport.h"
+
+using namespace RNS;
+using namespace RNS::Type::Interface;
+
+/*static*/ uint8_t Interface::DISCOVER_PATHS_FOR = MODE_ACCESS_POINT | MODE_GATEWAY;
+
+void InterfaceImpl::handle_outgoing(const Bytes& data) {
+ //TRACE("InterfaceImpl.handle_outgoing: data: " + data.toHex());
+ TRACE("InterfaceImpl.handle_outgoing");
+ _txb += data.size();
+}
+
+void InterfaceImpl::handle_incoming(const Bytes& data) {
+ //TRACE("InterfaceImpl.handle_incoming: data: " + data.toHex());
+ TRACE("InterfaceImpl.handle_incoming");
+ _rxb += data.size();
+ // Create temporary Interface encapsulating our own shared impl
+ std::shared_ptr<InterfaceImpl> self = shared_from_this();
+ Interface interface(self);
+ // Pass data on to transport for handling
+ Transport::inbound(data, interface);
+}
+
+void Interface::handle_incoming(const Bytes& data) {
+ //TRACE("Interface.handle_incoming: data: " + data.toHex());
+ TRACE("Interface.handle_incoming");
+ assert(_impl);
+/*
+ _impl->_rxb += data.size();
+ // Pass data on to transport for handling
+ Transport::inbound(data, *this);
+*/
+ _impl->handle_incoming(data);
+}
+
+void Interface::process_announce_queue() {
+/*
+ if not hasattr(self, "announce_cap"):
+ self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
+
+ if hasattr(self, "announce_queue"):
+ try:
+ now = time.time()
+ stale = []
+ for a in self.announce_queue:
+ if now > a["time"]+RNS.Reticulum.QUEUED_ANNOUNCE_LIFE:
+ stale.append(a)
+
+ for s in stale:
+ if s in self.announce_queue:
+ self.announce_queue.remove(s)
+
+ if len(self.announce_queue) > 0:
+ min_hops = min(entry["hops"] for entry in self.announce_queue)
+ entries = list(filter(lambda e: e["hops"] == min_hops, self.announce_queue))
+ entries.sort(key=lambda e: e["time"])
+ selected = entries[0]
+
+ double now = OS::time();
+ uint32_t wait_time = 0;
+ if (_impl->_bitrate > 0 && _impl->_announce_cap > 0) {
+ uint32_t tx_time = (len(selected["raw"])*8) / _impl->_bitrate;
+ wait_time = (tx_time / _impl->_announce_cap);
+ }
+ _impl->_announce_allowed_at = now + wait_time;
+
+ self.on_outgoing(selected["raw"])
+
+ if selected in self.announce_queue:
+ self.announce_queue.remove(selected)
+
+ if len(self.announce_queue) > 0:
+ timer = threading.Timer(wait_time, self.process_announce_queue)
+ timer.start()
+
+ except Exception as e:
+ self.announce_queue = []
+ RNS.log("Error while processing announce queue on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
+ RNS.log("The announce queue for this interface has been cleared.", RNS.LOG_ERROR)
+*/
+}
+
+/*
+void ArduinoJson::convertFromJson(JsonVariantConst src, RNS::Interface& dst) {
+ TRACE(">>> Deserializing Interface");
+TRACE(">>> Interface pre: " + dst.debugString());
+ if (!src.isNull()) {
+ RNS::Bytes hash;
+ hash.assignHex(src.as<const char*>());
+ TRACE(">>> Querying Transport for Interface hash " + hash.toHex());
+ // Query transport for matching interface
+ dst = Transport::find_interface_from_hash(hash);
+TRACE(">>> Interface post: " + dst.debugString());
+ }
+ else {
+ dst = {RNS::Type::NONE};
+TRACE(">>> Interface post: " + dst.debugString());
+ }
+}
+*/

diff --git a/lib/microReticulum/src/Interface.h b/lib/microReticulum/src/Interface.h
new file mode 100755
index 0000000..166b4be
--- /dev/null
+++ b/lib/microReticulum/src/Interface.h
@@ -0,0 +1,268 @@
+#pragma once
+
+#include "Identity.h"
+#include "Log.h"
+#include "Bytes.h"
+#include "Type.h"
+
+#include <ArduinoJson.h>
+
+#include <list>
+#include <memory>
+#include <cassert>
+#include <stdint.h>
+
+namespace RNS {
+
+ class Interface;
+ using HInterface = std::shared_ptr<Interface>;
+
+ class AnnounceEntry {
+ public:
+ AnnounceEntry() {}
+ AnnounceEntry(const Bytes& destination, double time, uint8_t hops, double emitted, const Bytes& raw) :
+ _destination(destination),
+ _time(time),
+ _hops(hops),
+ _emitted(emitted),
+ _raw(raw) {}
+ public:
+ Bytes _destination;
+ double _time = 0;
+ uint8_t _hops = 0;
+ uint64_t _emitted = 0;
+ Bytes _raw;
+ };
+
+ class InterfaceImpl : public std::enable_shared_from_this<InterfaceImpl> {
+
+ protected:
+ InterfaceImpl() { MEMF("InterfaceImpl object created, this: 0x%X", this); }
+ InterfaceImpl(const char* name) : _name(name) { MEMF("InterfaceImpl object created, this: 0x%X", this); }
+ public:
+ virtual ~InterfaceImpl() { MEMF("InterfaceImpl object destroyed, this: 0x%X", this); }
+
+ protected:
+ virtual bool start() { return true; }
+ virtual void stop() {}
+ virtual void loop() {}
+
+ // CBA Virtual override method for custom interface to send outgoing data
+ virtual void send_outgoing(const Bytes& data) = 0;
+
+ // CBA Internal method to handle housekeeping for data going out on interface
+ void handle_outgoing(const Bytes& data);
+ // CBA Internal method to handle data coming in on interface and pass on to transport
+ virtual void handle_incoming(const Bytes& data);
+
+ virtual const Bytes get_hash() const {
+ return Identity::full_hash({toString()});
+ }
+
+ virtual inline std::string toString() const { return "Interface[" + _name + "]"; }
+
+ protected:
+ Interface* _parent = nullptr;
+ bool _IN = false;
+ bool _OUT = false;
+ bool _FWD = false;
+ bool _RPT = false;
+ std::string _name;
+ size_t _rxb = 0;
+ size_t _txb = 0;
+ bool _online = false;
+ Bytes _ifac_identity;
+ Type::Interface::modes _mode = Type::Interface::MODE_NONE;
+ uint32_t _bitrate = 0;
+ uint16_t _HW_MTU = 0;
+ bool _AUTOCONFIGURE_MTU = false;
+ bool _FIXED_MTU = false;
+ double _announce_allowed_at = 0;
+ float _announce_cap = 0.0;
+ std::list<AnnounceEntry> _announce_queue;
+ bool _is_connected_to_shared_instance = false;
+ bool _is_local_shared_instance = false;
+ bool _is_backbone = false;
+ //Bytes _hash;
+ HInterface _parent_interface;
+ //Transport& _owner;
+
+ friend class Interface;
+ };
+
+ class Interface {
+
+ public:
+ // Which interface modes a Transport Node
+ // should actively discover paths for.
+ static uint8_t DISCOVER_PATHS_FOR;
+
+ public:
+ Interface(Type::NoneConstructor none) {
+ MEMF("Interface NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ Interface(const Interface& obj) : _impl(obj._impl) {
+ MEMF("Interface object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ Interface(std::shared_ptr<InterfaceImpl>& impl) : _impl(impl) {
+ MEMF("Interface object created with shared impl, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ Interface(InterfaceImpl* impl) : _impl(impl) {
+ MEMF("Interface object created with new impl, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+ virtual ~Interface() {
+ MEMF("Interface object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
+ }
+
+ inline Interface& operator = (const Interface& obj) {
+ _impl = obj._impl;
+ MEMF("Interface object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return *this;
+ }
+ inline Interface& operator = (InterfaceImpl* impl) {
+ _impl.reset(impl);
+ MEMF("Interface object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return *this;
+ }
+ inline operator bool() const {
+ MEMF("Interface object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() != nullptr;
+ }
+ inline bool operator < (const Interface& obj) const {
+ MEMF("Interface object <, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() < obj._impl.get();
+ }
+ inline bool operator > (const Interface& obj) const {
+ MEMF("Interface object <, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() > obj._impl.get();
+ }
+ inline bool operator == (const Interface& obj) const {
+ MEMF("Interface object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() == obj._impl.get();
+ }
+ inline bool operator != (const Interface& obj) const {
+ MEMF("Interface object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
+ return _impl.get() != obj._impl.get();
+ }
+ inline InterfaceImpl* get() {
+ return _impl.get();
+ }
+ inline void clear() {
+ _impl.reset();
+ }
+
+ public:
+ inline bool start() { assert(_impl); return _impl->start(); }
+ inline void stop() { assert(_impl); return _impl->stop(); }
+ inline void loop() { assert(_impl); return _impl->loop(); }
+ inline const Bytes get_hash() const { assert(_impl); return _impl->get_hash(); }
+ void process_announce_queue();
+
+ // CBA ACCUMULATES
+ inline void add_announce(AnnounceEntry& entry) { assert(_impl); _impl->_announce_queue.push_back(entry); }
+
+ protected:
+ inline void send_outgoing(const Bytes& data) { assert(_impl); _impl->send_outgoing(data); }
+ public:
+ //inline void handle_incoming(const Bytes& data) { assert(_impl); _impl->handle_incoming(data); }
+ // Public method to handle data coming in on interface and pass on to impl
+ void handle_incoming(const Bytes& data);
+
+ protected:
+ // setters
+ inline void IN(bool IN) { assert(_impl); _impl->_IN = IN; }
+ inline void OUT(bool OUT) { assert(_impl); _impl->_OUT = OUT; }
+ inline void FWD(bool FWD) { assert(_impl); _impl->_FWD = FWD; }
+ inline void RPT(bool RPT) { assert(_impl); _impl->_RPT = RPT; }
+ inline void name(const char* name) { assert(_impl); _impl->_name = name; }
+ inline void bitrate(uint32_t bitrate) { assert(_impl); _impl->_bitrate = bitrate; }
+ inline void online(bool online) { assert(_impl); _impl->_online = online; }
+ inline void announce_allowed_at(double announce_allowed_at) { assert(_impl); _impl->_announce_allowed_at = announce_allowed_at; }
+ public:
+ // getters
+ inline bool IN() const { assert(_impl); return _impl->_IN; }
+ inline bool OUT() const { assert(_impl); return _impl->_OUT; }
+ inline bool FWD() const { assert(_impl); return _impl->_FWD; }
+ inline bool RPT() const { assert(_impl); return _impl->_RPT; }
+ inline bool online() const { assert(_impl); return _impl->_online; }
+ inline std::string name() const { assert(_impl); return _impl->_name; }
+ inline const Bytes& ifac_identity() const { assert(_impl); return _impl->_ifac_identity; }
+ inline Type::Interface::modes mode() const { assert(_impl); return _impl->_mode; }
+ inline void mode(Type::Interface::modes mode) { assert(_impl); _impl->_mode = mode; }
+ inline uint32_t bitrate() const { assert(_impl); return _impl->_bitrate; }
+ inline uint16_t HW_MTU() const { assert(_impl); return _impl->_HW_MTU; }
+ inline bool AUTOCONFIGURE_MTU() const { assert(_impl); return _impl->_AUTOCONFIGURE_MTU; }
+ inline bool FIXED_MTU() const { assert(_impl); return _impl->_FIXED_MTU; }
+ inline double announce_allowed_at() const { assert(_impl); return _impl->_announce_allowed_at; }
+ inline float announce_cap() const { assert(_impl); return _impl->_announce_cap; }
+ inline std::list<AnnounceEntry>& announce_queue() const { assert(_impl); return _impl->_announce_queue; }
+ inline bool is_connected_to_shared_instance() const { assert(_impl); return _impl->_is_connected_to_shared_instance; }
+ inline bool is_local_shared_instance() const { assert(_impl); return _impl->_is_local_shared_instance; }
+ inline bool is_backbone() const { assert(_impl); return _impl->_is_backbone; }
+ inline void is_backbone(bool val) { assert(_impl); _impl->_is_backbone = val; }
+ inline HInterface parent_interface() const { assert(_impl); return _impl->_parent_interface; }
+
+ virtual inline std::string toString() const { if (!_impl) return ""; return _impl->toString(); }
+
+#ifndef NDEBUG
+ inline std::string debugString() const {
+ std::string dump;
+ dump = "Interface object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
+ return dump;
+ }
+#endif
+
+ protected:
+ std::shared_ptr<InterfaceImpl> _impl;
+
+ friend class Transport;
+ };
+
+}
+
+/*
+namespace ArduinoJson {
+ inline bool convertToJson(const RNS::Interface& src, JsonVariant dst) {
+ TRACE("<<< Serializing Interface");
+ if (!src) {
+ return dst.set(nullptr);
+ }
+ TRACE("<<< Interface hash " + src.get_hash().toHex());
+ return dst.set(src.get_hash().toHex());
+ }
+ void convertFromJson(JsonVariantConst src, RNS::Interface& dst);
+ inline bool canConvertFromJson(JsonVariantConst src, const RNS::Interface&) {
+ return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
+ }
+}
+*/
+/*
+namespace ArduinoJson {
+ template <>
+ struct Converter<RNS::Interface> {
+ static bool toJson(const RNS::Interface& src, JsonVariant dst) {
+ if (!src) {
+ return dst.set(nullptr);
+ }
+ TRACE("<<< Serializing interface hash " + src.get_hash().toHex());
+ return dst.set(src.get_hash().toHex());
+ }
+ static RNS::Interface fromJson(JsonVariantConst src) {
+ if (!src.isNull()) {
+ RNS::Bytes hash;
+ hash.assignHex(src.as<const char*>());
+ TRACE(">>> Deserialized interface hash " + hash.toHex());
+ TRACE(">>> Querying transport for interface");
+ // Query transport for matching interface
+ return RNS::Interface::find_interface_from_hash(hash);
+ }
+ else {
+ return {RNS::Type::NONE};
+ }
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
+ }
+ };
+}
+*/
\ No newline at end of file

diff --git a/lib/microReticulum/src/Link.cpp b/lib/microReticulum/src/Link.cpp
new file mode 100755
index 0000000..d358f8e
--- /dev/null
+++ b/lib/microReticulum/src/Link.cpp
@@ -0,0 +1,1836 @@
+#include "Link.h"
+
+#include "LinkData.h"
+#include "Resource.h"
+#include "Reticulum.h"
+#include "Transport.h"
+#include "Packet.h"
+#include "Log.h"
+#include "Cryptography/Ed25519.h"
+#include "Cryptography/X25519.h"
+#include "Cryptography/HKDF.h"
+#include "Cryptography/Token.h"
+#include "Cryptography/Random.h"
+#include "Utilities/OS.h"
+
+#define MSGPACK_DEBUGLOG_ENABLE 0
+#include <MsgPack.h>
+
+#include <math.h>
+
+#include <algorithm>
+
+using namespace RNS;
+using namespace RNS::Type::Link;
+using namespace RNS::Cryptography;
+using namespace RNS::Utilities;
+
+/*static*/ uint8_t Link::resource_strategies = ACCEPT_NONE | ACCEPT_APP | ACCEPT_ALL;
+
+/*static*/ std::set<link_mode> Link::ENABLED_MODES = {MODE_AES256_CBC};
+/*static*/ link_mode Link::MODE_DEFAULT = MODE_AES256_CBC;
+
+Link::Link(const Destination& destination /*= {Type::NONE}*/, Callbacks::established established_callback /*= nullptr*/, Callbacks::closed closed_callback /*= nullptr*/, const Destination& owner /*= {Type::NONE}*/, const Bytes& peer_pub_bytes /*= {Bytes::NONE}*/, const Bytes& peer_sig_pub_bytes /*= {Bytes::NONE}*/, link_mode mode /*= MODE_DEFAULT*/) :
+ _object(new LinkData(destination))
+{
+ assert(_object);
+
+ _object->_owner = owner;
+ _object->_mode = mode;
+
+ if (destination && destination.type() != Type::Destination::SINGLE) {
+ throw std::logic_error("Links can only be established to the \"single\" destination type");
+ }
+
+ if (!destination) {
+ _object->_initiator = false;
+ _object->_prv = Cryptography::X25519PrivateKey::generate();
+ // CBA BUG: not checking for owner
+ if (_object->_owner) {
+ _object->_sig_prv = _object->_owner.identity().sig_prv();
+ }
+ }
+ else {
+ _object->_initiator = true;
+ _object->_expected_hops = Transport::hops_to(_object->_destination.hash());
+ _object->_establishment_timeout = Reticulum::get_instance().get_first_hop_timeout(destination.hash());
+ _object->_prv = Cryptography::X25519PrivateKey::generate();
+ _object->_sig_prv = Cryptography::Ed25519PrivateKey::generate();
+ }
+
+ _object->_pub = _object->_prv->public_key();
+ _object->_pub_bytes = _object->_pub->public_bytes();
+ TRACEF("Link::load_private_key: pub bytes: %s", _object->_pub_bytes.toHex().c_str());
+
+ _object->_sig_pub = _object->_sig_prv->public_key();
+ _object->_sig_pub_bytes = _object->_sig_pub->public_bytes();
+ TRACEF("Link::load_private_key: sig pub bytes: %s", _object->_sig_pub_bytes.toHex().c_str());
+
+ if (!peer_pub_bytes) {
+ _object->_peer_pub = nullptr;
+ _object->_peer_pub_bytes = nullptr;
+ }
+ else {
+ load_peer(peer_pub_bytes, peer_sig_pub_bytes);
+ }
+
+ if (established_callback) {
+ set_link_established_callback(established_callback);
+ }
+
+ if (closed_callback) {
+ set_link_closed_callback(closed_callback);
+ }
+
+ if (_object->_initiator) {
+ Bytes signalling_bytes;
+ uint16_t nh_hw_mtu = Transport::next_hop_interface_hw_mtu(destination.hash());
+ if (RNS::Reticulum::link_mtu_discovery() && nh_hw_mtu) {
+ signalling_bytes = Link::signalling_bytes(nh_hw_mtu, _object->_mode);
+ DEBUGF("Signalling link MTU of %d for link", nh_hw_mtu);
+ }
+ else {
+ signalling_bytes = Link::signalling_bytes(RNS::Type::Reticulum::MTU, _object->_mode);
+ }
+ TRACEF("Establishing link with mode %d", _object->_mode);
+ //p self.request_data = self.pub_bytes+self.sig_pub_bytes+signalling_bytes
+ _object->_request_data = _object->_pub_bytes + _object->_sig_pub_bytes + signalling_bytes;
+ _object->_packet = Packet(destination, _object->_request_data, RNS::Type::Packet::LINKREQUEST);
+ _object->_packet.pack();
+ _object->_establishment_cost += _object->_packet.raw().size();
+ set_link_id(_object->_packet);
+ Transport::register_link(*this);
+ _object->_request_time = OS::time();
+ start_watchdog();
+ _object->_packet.send();
+ had_outbound();
+ DEBUGF("Link request %s sent to %s", _object->_link_id.toHex().c_str(), _object->_destination.toString().c_str());
+ TRACEF("Establishment timeout is %f for link request %s", _object->_establishment_timeout, _object->_link_id.toHex().c_str());
+ }
+
+ // CBA LINK
+ //_object->_link_destination = Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::LINK, hash());
+ //_object->_link_destination.link_id(_object->_link_id);
+
+ MEM("Link object created");
+}
+
+
+/*static*/ Bytes Link::signalling_bytes(uint16_t mtu, link_mode mode) {
+ //p if not mode in Link.ENABLED_MODES: raise TypeError(f"Requested link mode {Link.MODE_DESCRIPTIONS[mode]} not enabled")
+ //if (!(mode & ENABLED_MODES)) throw std::runtime_error("Requested link mode "+std::to_string(mode)+" not enabled");
+ if (Link::ENABLED_MODES.find(mode) == Link::ENABLED_MODES.end()) throw std::runtime_error("Requested link mode "+std::to_string(mode)+" not enabled");
+ //p signalling_value = (mtu & Link.MTU_BYTEMASK)+(((mode<<5) & Link.MODE_BYTEMASK)<<16)
+ //p return struct.pack(">I", signalling_value)[1:]
+ uint32_t signalling_value = OS::portable_htonl((mtu & MTU_BYTEMASK)+(((mode<<5) & MODE_BYTEMASK)<<16));
+ Bytes data(((uint8_t*)&signalling_value)+1, 3);
+ return data;
+}
+
+/*static*/ uint16_t Link::mtu_from_lr_packet(const Packet& packet) {
+ if (packet.data().size() == ECPUBSIZE+LINK_MTU_SIZE) {
+ return (packet.data()[ECPUBSIZE] << 16) + (packet.data()[ECPUBSIZE+1] << 8) + (packet.data()[ECPUBSIZE+2]) & MTU_BYTEMASK;
+ }
+ return 0;
+}
+
+/*static*/ uint16_t Link::mtu_from_lp_packet(const Packet& packet) {
+ //p if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
+ if (packet.data().size() == RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2+LINK_MTU_SIZE) {
+ Bytes mtu_bytes = packet.data().mid(RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2, LINK_MTU_SIZE);
+ return (mtu_bytes[0] << 16) + (mtu_bytes[1] << 8) + (mtu_bytes[2]) & MTU_BYTEMASK;
+ }
+ return 0;
+}
+
+/*static*/ uint8_t Link::mode_byte(link_mode mode) {
+ //p if mode in Link.ENABLED_MODES: return (mode << 5) & Link.MODE_BYTEMASK
+ if (Link::ENABLED_MODES.find(mode) != Link::ENABLED_MODES.end()) return (mode << 5) & MODE_BYTEMASK;
+ throw std::runtime_error("Requested link mode {mode} not enabled");
+}
+
+/*static*/ link_mode Link::mode_from_lr_packet(const Packet& packet) {
+ if (packet.data().size() > ECPUBSIZE) {
+ link_mode mode = static_cast<link_mode>((packet.data()[ECPUBSIZE] & MODE_BYTEMASK) >> 5);
+ return mode;
+ }
+ return Link::MODE_DEFAULT;
+}
+
+/*static*/ link_mode Link::mode_from_lp_packet(const Packet& packet) {
+ if (packet.data().size() > RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2) {
+ link_mode mode = static_cast<link_mode>(packet.data()[RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2] >> 5);
+ return mode;
+ }
+ return Link::MODE_DEFAULT;
+}
+
+/*static*/ Bytes Link::link_id_from_lr_packet(const Packet& packet) {
+ Bytes hashable_part = packet.get_hashable_part();
+ if (packet.data().size() > ECPUBSIZE) {
+ size_t diff = packet.data().size() - ECPUBSIZE;
+ //p hashable_part = hashable_part[:-diff]
+ hashable_part = hashable_part.left(hashable_part.size() - diff);
+ }
+ return RNS::Identity::truncated_hash(hashable_part);
+}
+
+/*static*/ Link Link::validate_request( const Destination& owner, const Bytes& data, const Packet& packet) {
+ if (data.size() == ECPUBSIZE || data.size() == ECPUBSIZE + LINK_MTU_SIZE) {
+ try {
+ Link link({Type::NONE}, nullptr, nullptr, owner, data.left(ECPUBSIZE/2), data.mid(ECPUBSIZE/2, ECPUBSIZE/2));
+ link.set_link_id(packet);
+
+ if (data.size() == ECPUBSIZE + LINK_MTU_SIZE) {
+ DEBUG("Link request includes MTU signalling"); // TODO: Remove debug
+ try {
+ uint16_t mtu = mtu_from_lr_packet(packet);
+ link.mtu((mtu != 0) ? mtu : Type::Reticulum::MTU);
+ }
+ catch (std::exception& e) {
+ ERRORF("An error ocurred while validating link request %s", link.link_id().toHex().c_str());
+ link.mtu(Type::Reticulum::MTU);
+ }
+ }
+
+ link.mode(mode_from_lr_packet(packet));
+
+ // TODO: Remove debug
+ DEBUGF("Incoming link request with mode %d", link.get_mode());
+
+ link.update_mdu();
+ link.destination(packet.destination());
+ link.establishment_timeout(ESTABLISHMENT_TIMEOUT_PER_HOP * std::max((uint8_t)1, packet.hops()) + KEEPALIVE);
+ link.establishment_cost(link.establishment_cost() + packet.raw().size());
+ VERBOSEF("Validating link request %s", link.link_id().toHex().c_str());
+ TRACEF("Link MTU configured to %d", link.mtu());
+ TRACEF("Establishment timeout is %f for incoming link request %s", link.establishment_timeout(), link.link_id().toHex().c_str());
+ link.handshake();
+ link.attached_interface(packet.receiving_interface());
+ link.prove();
+ link.request_time(OS::time());
+ Transport::register_link(link);
+ link.last_inbound(OS::time());
+ link.start_watchdog();
+
+ DEBUGF("Incoming link request %s accepted", link.toString().c_str());
+ return link;
+ }
+ catch (std::exception& e) {
+ VERBOSEF("Validating link request failed, exception: %s", e.what());
+ return {Type::NONE};
+ }
+ }
+ else {
+ DEBUGF("Invalid link request payload size (%zu), dropping request", data.size());
+ return {Type::NONE};
+ }
+}
+
+void Link::load_peer(const Bytes& peer_pub_bytes, const Bytes& peer_sig_pub_bytes) {
+ assert(_object);
+ _object->_peer_pub_bytes = peer_pub_bytes;
+ _object->_peer_pub = Cryptography::X25519PublicKey::from_public_bytes(_object->_peer_pub_bytes);
+
+ _object->_peer_sig_pub_bytes = peer_sig_pub_bytes;
+ _object->_peer_sig_pub = Cryptography::Ed25519PublicKey::from_public_bytes(_object->_peer_sig_pub_bytes);
+
+ // CBA TODO Determine the purpose of the following.
+ // X25519PublicKey does not have a member "curve" and this is not accessed anywhere
+/*
+ if not hasattr(self.peer_pub, "curve") {
+ self.peer_pub.curve = CURVE;
+ }
+*/
+}
+
+void Link::set_link_id(const Packet& packet) {
+ assert(_object);
+ _object->_link_id = Link::link_id_from_lr_packet(packet);
+ _object->_hash = _object->_link_id;
+}
+
+void Link::handshake() {
+ assert(_object);
+ if (_object->_status == Type::Link::PENDING && _object->_prv) {
+ _object->_status = Type::Link::HANDSHAKE;
+ _object->_shared_key = _object->_prv->exchange(_object->_peer_pub_bytes);
+
+ uint16_t derived_key_length;
+ if (_object->_mode == RNS::Type::Link::MODE_AES128_CBC) derived_key_length = 32;
+ else if (_object->_mode == RNS::Type::Link::MODE_AES256_CBC) derived_key_length = 64;
+ else throw new std::invalid_argument("Invalid link mode "+std::to_string(_object->_mode)+" on "+toString());
+
+ _object->_derived_key = Cryptography::hkdf(
+ derived_key_length,
+ _object->_shared_key,
+ get_salt(),
+ get_context()
+ );
+ }
+ else {
+ ERRORF("Handshake attempt on %s with invalid state %d", toString().c_str(), _object->_status);
+ }
+}
+
+void Link::prove() {
+ assert(_object);
+ DEBUGF("Link %s requesting proof", link_id().toHex().c_str());
+ Bytes signed_data =_object->_link_id + _object->_pub_bytes + _object->_sig_pub_bytes;
+ const Bytes signature(_object->_owner.identity().sign(signed_data));
+
+ Bytes proof_data = signature + _object->_pub_bytes;
+ // CBA LINK
+ // CBA TODO: Determine which approach is better, passing liunk to packet or passing _link_destination
+ Packet proof(*this, proof_data, Type::Packet::PROOF, Type::Packet::LRPROOF);
+ proof.send();
+ _object->_establishment_cost += proof.raw().size();
+ had_outbound();
+}
+
+void Link::prove_packet(const Packet& packet) {
+ assert(_object);
+ DEBUGF("Link %s proving packet", link_id().toHex().c_str());
+ const Bytes signature(sign(packet.packet_hash()));
+ // TODO: Hardcoded as explicit proof for now
+ // if RNS.Reticulum.should_use_implicit_proof():
+ // proof_data = signature
+ // else:
+ // proof_data = packet.packet_hash + signature
+ Bytes proof_data = packet.packet_hash() + signature;
+
+ Packet proof(*this, proof_data, Type::Packet::PROOF);
+ proof.send();
+ had_outbound();
+}
+
+void Link::validate_proof(const Packet& packet) {
+ assert(_object);
+ DEBUGF("Link %s validating proof", link_id().toHex().c_str());
+ try {
+ if (_object->_status == Type::Link::PENDING) {
+ Bytes packet_data(packet.data());
+ TRACEF("Link %s: initiator: %d", toString().c_str(), _object->_initiator);
+ TRACEF("Link %s: size: %d", toString().c_str(), packet_data.size());
+ Bytes signalling_bytes;
+ uint16_t confirmed_mtu = 0;
+ link_mode mode = mode_from_lp_packet(packet);
+ DEBUGF("Validating link request proof with mode %d", mode);
+ if (mode != _object->_mode) throw std::runtime_error("Invalid link mode "+std::to_string(mode)+" in link request proof");
+ //p if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
+ if (packet_data.size() == Type::Identity::SIGLENGTH/8+ECPUBSIZE/2+RNS::Type::Link::LINK_MTU_SIZE) {
+ confirmed_mtu = Link::mtu_from_lp_packet(packet);
+ signalling_bytes = Link::signalling_bytes(confirmed_mtu, mode);
+ // CBA TODO Determine best way to deal with packet.data() being read-only
+ //p packet.data = packet.data[:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2]
+ packet_data = packet_data.left(RNS::Type::Identity::SIGLENGTH/8+ECPUBSIZE/2);
+ DEBUGF("Destination confirmed link MTU of %d", confirmed_mtu);
+ }
+ //p if _object->_initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8+ECPUBSIZE//2:
+ if (_object->_initiator && packet_data.size() == Type::Identity::SIGLENGTH/8+ECPUBSIZE/2) {
+ //p peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+ECPUBSIZE//2]
+ const Bytes peer_pub_bytes(packet_data.mid(Type::Identity::SIGLENGTH/8, ECPUBSIZE/2));
+ //p peer_sig_pub_bytes = _object->_destination.identity.get_public_key()[ECPUBSIZE//2:ECPUBSIZE]
+ const Bytes peer_sig_pub_bytes(_object->_destination.identity().get_public_key().mid(ECPUBSIZE/2, ECPUBSIZE/2));
+ TRACEF("Link %s performing handshake", link_id().toHex().c_str());
+ load_peer(peer_pub_bytes, peer_sig_pub_bytes);
+ handshake();
+
+ _object->_establishment_cost += packet.raw().size();
+ Bytes signed_data = _object->_link_id + _object->_peer_pub_bytes + _object->_peer_sig_pub_bytes;
+ const Bytes signature(packet_data.left(Type::Identity::SIGLENGTH/8));
+
+ TRACEF("Link %s validating identity", link_id().toHex().c_str());
+ if (_object->_destination.identity().validate(signature, signed_data)) {
+ if (_object->_status != Type::Link::HANDSHAKE) {
+ throw std::runtime_error("Invalid link state for proof validation: " + _object->_status);
+ }
+ _object->_rtt = OS::time() - _object->_request_time;
+ _object->_attached_interface = packet.receiving_interface();
+ _object->__remote_identity = _object->_destination.identity();
+ if (confirmed_mtu) _object->_mtu = confirmed_mtu;
+ else _object->_mtu = RNS::Type::Reticulum::MTU;
+ update_mdu();
+ _object->_status = Type::Link::ACTIVE;
+ _object->_activated_at = OS::time();
+ _object->_last_proof = _object->_activated_at;
+ Transport::activate_link(*this);
+ VERBOSEF("Link %s established with %s, RTT is %f s", toString().c_str(), _object->_destination.toString().c_str(), OS::round(_object->_rtt, 3));
+
+ //p if _object->_rtt != None and _object->_establishment_cost != None and _object->_rtt > 0 and _object->_establishment_cost > 0:
+ if (_object->_rtt != 0.0 && _object->_establishment_cost != 0 && _object->_rtt > 0 and _object->_establishment_cost > 0) {
+ _object->_establishment_rate = _object->_establishment_cost / _object->_rtt;
+ }
+
+ //p rtt_data = umsgpack.packb(self.rtt)
+ MsgPack::Packer packer;
+ packer.serialize(_object->_rtt);
+ Bytes rtt_data(packer.data(), packer.size());
+TRACEF("***** RTT data size: %d", rtt_data.size());
+ //p rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
+ Packet rtt_packet(*this, rtt_data, Type::Packet::DATA, Type::Packet::LRRTT);
+TRACEF("***** RTT packet data: %s", rtt_packet.data().toHex().c_str());
+rtt_packet.pack();
+Packet test_packet(RNS::Destination(RNS::Type::NONE), rtt_packet.raw());
+test_packet.unpack();
+TRACEF("***** RTT test packet destination hash: %s", test_packet.destination_hash().toHex().c_str());
+TRACEF("***** RTT test packet data size: %d", test_packet.data().size());
+TRACEF("***** RTT test packet data: %s", test_packet.data().toHex().c_str());
+Bytes plaintext = decrypt(test_packet.data());
+TRACEF("***** RTT test packet plaintext: %s", plaintext.toHex().c_str());
+ rtt_packet.send();
+ had_outbound();
+
+ if (_object->_callbacks._established != nullptr) {
+ VERBOSEF("Link %s is established", link_id().toHex().c_str());
+ //p thread = threading.Thread(target=_object->_callbacks.link_established, args=(self,))
+ //p thread.daemon = True
+ //p thread.start()
+ _object->_callbacks._established(*this);
+ }
+ }
+ else {
+ DEBUGF("Invalid link proof signature received by %s. Ignoring.", toString().c_str());
+ }
+ }
+ else {
+ DEBUGF("Failed initiator/size check for link proof signature received by %s. Ignoring.", toString().c_str());
+ }
+ }
+ }
+ catch (std::exception& e) {
+ _object->_status = Type::Link::CLOSED;
+ ERRORF("An error ocurred while validating link request proof on %s.", toString().c_str());
+ ERRORF("The contained exception was: %s", e.what());
+ }
+}
+
+
+/*
+Identifies the initiator of the link to the remote peer. This can only happen
+once the link has been established, and is carried out over the encrypted link.
+The identity is only revealed to the remote peer, and initiator anonymity is
+thus preserved. This method can be used for authentication.
+
+:param identity: An RNS.Identity instance to identify as.
+*/
+void Link::identify(const Identity& identity) {
+ assert(_object);
+ DEBUGF("Link %s requesting identity", link_id().toHex().c_str());
+ if (_object->_initiator && _object->_status == Type::Link::ACTIVE) {
+ const Bytes signed_data(_object->_link_id + identity.get_public_key());
+ const Bytes signature(identity.sign(signed_data));
+ const Bytes proof_data(identity.get_public_key() + signature);
+
+ Packet proof(*this, proof_data, Type::Packet::DATA, Type::Packet::LINKIDENTIFY);
+ proof.send();
+ had_outbound();
+ }
+}
+
+/*
+Sends a request to the remote peer.
+
+:param path: The request path.
+:param response_callback: An optional function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example<example-request>` for more info.
+:param failed_callback: An optional function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example<example-request>` for more info.
+:param progress_callback: An optional function or method with the signature *progress_callback(request_receipt)* to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the *request_receipt.progress* property.
+:param timeout: An optional timeout in seconds for the request. If *None* is supplied it will be calculated based on link RTT.
+:returns: A :ref:`RNS.RequestReceipt<api-requestreceipt>` instance if the request was sent, or *False* if it was not.
+*/
+const RNS::RequestReceipt Link::request(const Bytes& path, const Bytes& data /*= {Bytes::NONE}*/, RequestReceipt::Callbacks::response response_callback /*= nullptr*/, RequestReceipt::Callbacks::failed failed_callback /*= nullptr*/, RequestReceipt::Callbacks::progress progress_callback /*= nullptr*/, double timeout /*= 0.0*/) {
+ assert(_object);
+ DEBUGF("Link %s sending request", link_id().toHex().c_str());
+ const Bytes request_path_hash(Identity::truncated_hash(path));
+
+ //p unpacked_request = [OS::time(), request_path_hash, data]
+ //p packed_request = umsgpack.packb(unpacked_request)
+ MsgPack::Packer packer;
+ packer.to_array(OS::time(), request_path_hash, data);
+ Bytes packed_request(packer.data(), packer.size());
+
+ if (timeout == 0.0) {
+ timeout = _object->_rtt * _object->_traffic_timeout_factor + Type::Resource::RESPONSE_MAX_GRACE_TIME * 1.125;
+ }
+
+ if (packed_request.size() <= MDU) {
+ Packet request_packet(*this, packed_request, Type::Packet::DATA, Type::Packet::REQUEST);
+ PacketReceipt packet_receipt = request_packet.send();
+
+ if (!packet_receipt) {
+ return {Type::NONE};
+ }
+ else {
+ packet_receipt.set_timeout(timeout);
+ return RequestReceipt(
+ *this,
+ packet_receipt,
+ {Type::NONE},
+ response_callback,
+ failed_callback,
+ progress_callback,
+ timeout,
+ packed_request.size()
+ );
+ }
+ }
+ else {
+ const Bytes request_id(Identity::truncated_hash(packed_request));
+ DEBUGF("Sending request %s as resource.", request_id.toHex().c_str());
+ Resource request_resource(packed_request, *this, request_id, false, timeout);
+
+ return RequestReceipt(
+ *this,
+ {Type::NONE},
+ request_resource,
+ response_callback,
+ failed_callback,
+ progress_callback,
+ timeout,
+ packed_request.size()
+ );
+ }
+}
+
+
+void Link::update_mdu() {
+ assert(_object);
+ _object->_mdu = _object->_mtu - RNS::Type::Reticulum::HEADER_MAXSIZE - RNS::Type::Reticulum::IFAC_MIN_SIZE;
+ //p self.mdu = math.floor((self.mtu-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.TOKEN_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
+ _object->_mdu = floor((_object->_mtu-RNS::Type::Reticulum::IFAC_MIN_SIZE-RNS::Type::Reticulum::HEADER_MINSIZE-RNS::Type::Identity::TOKEN_OVERHEAD)/RNS::Type::Identity::AES128_BLOCKSIZE)*RNS::Type::Identity::AES128_BLOCKSIZE - 1;
+}
+
+void Link::rtt_packet(const Packet& packet) {
+ assert(_object);
+ try {
+ double measured_rtt = OS::time() - _object->_request_time;
+ const Bytes plaintext(decrypt(packet.data()));
+ if (plaintext) {
+ //p rtt = umsgpack.unpackb(plaintext)
+ MsgPack::Unpacker unpacker;
+ unpacker.feed(plaintext.data(), plaintext.size());
+ double rtt = 0.0;
+ unpacker.deserialize(rtt);
+ _object->_rtt = std::max(measured_rtt, rtt);
+ _object->_status = Type::Link::ACTIVE;
+ _object->_activated_at = OS::time();
+
+ //p if _object->_rtt != None and _object->_establishment_cost != None and _object->_rtt > 0 and _object->_establishment_cost > 0:
+ if (_object->_rtt != 0.0 && _object->_establishment_cost != 0.0 && _object->_rtt > 0 and _object->_establishment_cost > 0) {
+ _object->_establishment_rate = _object->_establishment_cost / _object->_rtt;
+ }
+
+ try {
+ if (_object->_owner.callbacks()._link_established != nullptr) {
+ _object->_owner.callbacks()._link_established(*this);
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Error occurred in external link establishment callback. The contained exception was: %s", e.what());
+ }
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Error occurred while processing RTT packet, tearing down link. The contained exception was: %s", e.what());
+ teardown();
+ }
+}
+
+/*
+:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second.
+*/
+float Link::get_establishment_rate() {
+ assert(_object);
+ //p if _object->_establishment_rate != None:
+ //p return _object->_establishment_rate*8
+ //p else:
+ //p return None
+ return _object->_establishment_rate*8;
+}
+
+/*
+:returns: The MTU of an established link.
+*/
+uint16_t Link::get_mtu() {
+ assert(_object);
+ if (_object->_status == ACTIVE) {
+ return _object->_mtu;
+ }
+ return 0;
+}
+
+/*
+:returns: The packet MDU of an established link.
+*/
+uint16_t Link::get_mdu() {
+ assert(_object);
+ if (_object->_status == ACTIVE) {
+ return _object->_mdu;
+ }
+ return 0;
+}
+
+/*
+:returns: The packet expected in-flight data rate of an established link.
+*/
+float Link::get_expected_rate() {
+ assert(_object);
+ if (_object->_status == ACTIVE) {
+ return _object->_expected_rate;
+ }
+ return 0.0;
+}
+
+/*
+:returns: The mode of an established link.
+*/
+link_mode Link::get_mode() {
+ assert(_object);
+ return _object->_mode;
+}
+
+const Bytes& Link::get_salt() {
+ assert(_object);
+ return _object->_link_id;
+}
+
+const Bytes Link::get_context() {
+ return {Bytes::NONE};
+}
+
+/*
+:returns: The time in seconds since this link was established.
+*/
+double Link::get_age() {
+ assert(_object);
+ if (_object->_activated_at) {
+ return OS::time() - _object->_activated_at;
+ }
+ return 0.0;
+}
+
+/*
+:returns: The time in seconds since last inbound packet on the link. This includes keepalive packets.
+*/
+double Link::no_inbound_for() {
+ assert(_object);
+ //p activated_at = _object->_activated_at if _object->_activated_at != None else 0
+ double activated_at = _object->_activated_at;
+ double last_inbound = std::max(_object->_last_inbound, activated_at);
+ return (OS::time() - last_inbound);
+}
+
+/*
+:returns: The time in seconds since last outbound packet on the link. This includes keepalive packets.
+*/
+double Link::no_outbound_for() {
+ assert(_object);
+ return OS::time() - _object->_last_outbound;
+}
+
+/*
+:returns: The time in seconds since payload data traversed the link. This excludes keepalive packets.
+*/
+double Link::no_data_for() {
+ assert(_object);
+ return OS::time() - _object->_last_data;
+}
+
+/*
+:returns: The time in seconds since activity on the link. This includes keepalive packets.
+*/
+double Link::inactive_for() {
+ assert(_object);
+ return std::min(no_inbound_for(), no_outbound_for());
+}
+
+/*
+:returns: The identity of the remote peer, if it is known. Calling this method will not query the remote initiator to reveal its identity. Returns ``None`` if the link initiator has not already independently called the ``identify(identity)`` method.
+*/
+const Identity& Link::get_remote_identity() {
+ assert(_object);
+ return _object->__remote_identity;
+}
+
+void Link::had_outbound(bool is_keepalive /*= false*/) {
+ assert(_object);
+ _object->_last_outbound = OS::time();
+ if (!is_keepalive) {
+ _object->_last_data = _object->_last_outbound;
+ }
+}
+
+/*
+Closes the link and purges encryption keys. New keys will
+be used if a new link to the same destination is established.
+*/
+void Link::teardown() {
+ assert(_object);
+ if (_object->_status != Type::Link::PENDING && _object->_status != Type::Link::CLOSED) {
+ Packet teardown_packet(*this, _object->_link_id, Type::Packet::DATA, Type::Packet::LINKCLOSE);
+ teardown_packet.send();
+ had_outbound();
+ }
+ _object->_status = Type::Link::CLOSED;
+ if (_object->_initiator) {
+ _object->_teardown_reason = Type::Link::INITIATOR_CLOSED;
+ }
+ else {
+ _object->_teardown_reason = Type::Link::DESTINATION_CLOSED;
+ }
+ link_closed();
+}
+
+void Link::teardown_packet(const Packet& packet) {
+ assert(_object);
+ try {
+ Bytes plaintext = decrypt(packet.data());
+ if (plaintext == _object->_link_id) {
+ _object->_status = Type::Link::CLOSED;
+ if (_object->_initiator) {
+ _object->_teardown_reason = Type::Link::DESTINATION_CLOSED;
+ }
+ else {
+ _object->_teardown_reason = Type::Link::INITIATOR_CLOSED;
+ }
+ link_closed();
+ }
+ }
+ catch (std::exception& e) {
+ }
+}
+
+void Link::link_closed() {
+ assert(_object);
+ for (auto& resource : _object->_incoming_resources) {
+ const_cast<Resource&>(resource).cancel();
+ }
+ for (auto& resource : _object->_outgoing_resources) {
+ const_cast<Resource&>(resource).cancel();
+ }
+ if (_object->_channel) {
+ _object->_channel._shutdown();
+ }
+
+ _object->_prv.reset();
+ _object->_pub.reset();
+ _object->_pub_bytes.clear();
+ _object->_shared_key.clear();
+ _object->_derived_key.clear();
+
+ if (_object->_destination) {
+ if (_object->_destination.direction() == Type::Destination::IN) {
+ if (_object->_destination.has_link(*this)) {
+ _object->_destination.remove_link(*this);
+ }
+ }
+ }
+
+ if (_object->_callbacks._closed) {
+ try {
+ _object->_callbacks._closed(*this);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing link closed callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+}
+
+// CBA TODO Implement watchdog
+void Link::start_watchdog() {
+ //z thread = threading.Thread(target=_object->___watchdog_job)
+ //z thread.daemon = True
+ //z thread.start()
+}
+
+/*p TODO
+
+void Link::__watchdog_job() {
+ assert(_object);
+ while not _object->_status == Type::Link::CLOSED:
+ while (_object->_watchdog_lock):
+ rtt_wait = 0.025
+ if hasattr(self, "rtt") and _object->_rtt:
+ rtt_wait = _object->_rtt
+
+ sleep(max(rtt_wait, 0.025))
+
+ if not _object->_status == Type::Link::CLOSED:
+ # Link was initiated, but no response
+ # from destination yet
+ if _object->_status == PENDING:
+ next_check = _object->_request_time + _object->_establishment_timeout
+ sleep_time = next_check - OS::time()
+ if OS::time() >= _object->_request_time + _object->_establishment_timeout:
+ RNS.log("Link establishment timed out", RNS.LOG_VERBOSE)
+ _object->_status = Type::Link::CLOSED
+ _object->_teardown_reason = TIMEOUT
+ link_closed()
+ sleep_time = 0.001
+
+ elif _object->_status == Type::Link::HANDSHAKE:
+ next_check = _object->_request_time + _object->_establishment_timeout
+ sleep_time = next_check - OS::time()
+ if OS::time() >= _object->_request_time + _object->_establishment_timeout:
+ _object->_status = Type::Link::CLOSED
+ _object->_teardown_reason = TIMEOUT
+ link_closed()
+ sleep_time = 0.001
+
+ if _object->_initiator:
+ RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG)
+ else:
+ RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
+
+ elif _object->_status == Type::Link::ACTIVE:
+ activated_at = _object->_activated_at if _object->_activated_at != None else 0
+ last_inbound = max(max(_object->_last_inbound, _object->_last_proof), activated_at)
+
+ if OS::time() >= last_inbound + _object->_keepalive:
+ if _object->_initiator:
+ send_keepalive()
+
+ if OS::time() >= last_inbound + _object->_stale_time:
+ sleep_time = _object->_rtt * _object->_keepalive_timeout_factor + STALE_GRACE
+ _object->_status = STALE
+ else:
+ sleep_time = _object->_keepalive
+
+ else:
+ sleep_time = (last_inbound + _object->_keepalive) - OS::time()
+
+ elif _object->_status == STALE:
+ sleep_time = 0.001
+ _object->_status = Type::Link::CLOSED
+ _object->_teardown_reason = TIMEOUT
+ link_closed()
+
+
+ if sleep_time == 0:
+ RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_ERROR)
+ if sleep_time == None or sleep_time < 0:
+ RNS.log("Timing error! Tearing down link "+str(self)+" now.", RNS.LOG_ERROR)
+ teardown()
+ sleep_time = 0.1
+
+ sleep(sleep_time)
+
+*/
+
+void Link::send_keepalive() {
+ assert(_object);
+ //p keepalive_packet = RNS.Packet(self, bytes([0xFF]), context=RNS.Packet.KEEPALIVE)
+ RNS::Packet keepalive_packet(*this, Bytes("\xFF"), Type::Packet::DATA, Type::Packet::KEEPALIVE);
+ keepalive_packet.send();
+ had_outbound(true);
+}
+
+void Link::handle_request(const Bytes& request_id, const ResourceRequest& resource_request) {
+ assert(_object);
+ DEBUGF("Link %s handling request", link_id().toHex().c_str());
+ if (_object->_status == Type::Link::ACTIVE) {
+ //p requested_at = unpacked_request[0]
+ //p path_hash = unpacked_request[1]
+ //p request_data = unpacked_request[2]
+
+ auto handler_iter = _object->_destination.request_handlers().find(resource_request._path_hash);
+ if (handler_iter != _object->_destination.request_handlers().end()) {
+ TRACE("Link::handle_request: Found handler");
+ RequestHandler request_handler = (*handler_iter).second;
+
+ bool allowed = false;
+ if (request_handler._allow != Type::Destination::ALLOW_NONE) {
+ if (request_handler._allow == Type::Destination::ALLOW_LIST) {
+ if (_object->__remote_identity && request_handler._allowed_list.count(_object->__remote_identity.hash()) > 0) {
+ allowed = true;
+ }
+ }
+ else if (request_handler._allow == Type::Destination::ALLOW_ALL) {
+ allowed = true;
+ }
+ }
+
+ if (allowed) {
+ DEBUGF("Handling request %s for: %s", request_id.toHex().c_str(), request_handler._path.toString().c_str());
+ //p if len(inspect.signature(response_generator).parameters) == 5:
+ //p response = response_generator(path, request_data, request_id, _object->__remote_identity, requested_at)
+ //p elif len(inspect.signature(response_generator).parameters) == 6:
+ //p response = response_generator(path, request_data, request_id, _object->_link_id, _object->__remote_identity, requested_at)
+ //p else:
+ //p raise TypeError("Invalid signature for response generator callback")
+ Bytes response(request_handler._response_generator(request_handler._path, resource_request._request_data, request_id, _object->_link_id, _object->__remote_identity, resource_request._requested_at));
+
+ if (response) {
+ //p packed_response = umsgpack.packb([request_id, response])
+ MsgPack::Packer packer;
+ packer.to_array(request_id, response);
+ Bytes packed_response(packer.data(), packer.size());
+
+ if (packed_response.size() <= MDU) {
+ //p RNS.Packet(self, packed_response, Type::Packet::DATA, context = Type::Packet::RESPONSE).send()
+ RNS::Packet response_packet(*this, packed_response, Type::Packet::DATA, Type::Packet::RESPONSE);
+ response_packet.send();
+ }
+ else {
+ // CBA TODO Determine why unused Resource is created here
+ Resource response_resource = RNS::Resource(packed_response, *this, request_id, true);
+ }
+ }
+ }
+ else {
+ std::string identity_string;
+ if (get_remote_identity()) {
+ identity_string = get_remote_identity().toString();
+ }
+ else {
+ identity_string = "<Unknown>";
+ }
+ DEBUGF("Request %s from %s not allowed for: %s", request_id.toHex().c_str(), identity_string.c_str(), request_handler._path.toString().c_str());
+ }
+ }
+ }
+}
+
+void Link::handle_response(const Bytes& request_id, const Bytes& response_data, size_t response_size, size_t response_transfer_size) {
+ assert(_object);
+ if (_object->_status == Type::Link::ACTIVE) {
+ RNS::RequestReceipt remove = {Type::NONE};
+ for (RNS::RequestReceipt pending_request : _object->_pending_requests) {
+ if (pending_request.request_id() == request_id) {
+ remove = pending_request;
+ try {
+ pending_request.response_size(response_size);
+ //if (pending_request.response_transfer_size == 0) {
+ // pending_request.response_transfer_size = 0;
+ //}
+ pending_request.response_transfer_size(pending_request.response_transfer_size() + response_transfer_size);
+ pending_request.response_received(response_data);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error occurred while handling response. The contained exception was: %s", e.what());
+ }
+ break;
+ }
+ }
+ if (remove) {
+ if (_object->_pending_requests.count(remove) > 0) {
+ _object->_pending_requests.erase(remove);
+ }
+ }
+ }
+}
+
+void Link::request_resource_concluded(const Resource& resource) {
+ assert(_object);
+ if (resource.status() == Type::Resource::COMPLETE) {
+ //p packed_request = resource.data().read()
+ Bytes packed_request = resource.data();
+ //p unpacked_request = umsgpack.unpackb(packed_request)
+ MsgPack::Unpacker unpacker;
+ unpacker.feed(packed_request.data(), packed_request.size());
+ // CBA TODO OPTIMIZE MSGPACK
+ double requested_at;
+ MsgPack::bin_t<uint8_t> path_hash;
+ MsgPack::bin_t<uint8_t> request_data;
+ unpacker.from_array(requested_at, path_hash, request_data);
+ ResourceRequest resource_request;
+ resource_request._requested_at = requested_at;
+ resource_request._path_hash = path_hash;
+ resource_request._request_data = request_data;
+ //p request_id = RNS.Identity.truncated_hash(packed_request)
+ Bytes request_id(Identity::truncated_hash(resource.data()));
+ //p request_data = unpacked_request
+
+ //p handle_request(request_id, request_data)
+ handle_request(request_id, resource_request);
+ }
+ else {
+ DEBUGF("Incoming request resource failed with status: %d", resource.status());
+ }
+}
+
+void Link::response_resource_concluded(const Resource& resource) {
+ assert(_object);
+ if (resource.status() == Type::Resource::COMPLETE) {
+ //p packed_response = resource.data.read()
+ Bytes packed_response = resource.data();
+ //p unpacked_response = umsgpack.unpackb(packed_response)
+ //p request_id = unpacked_response[0]
+ //p response_data = unpacked_response[1]
+ MsgPack::Unpacker unpacker;
+ unpacker.feed(packed_response.data(), packed_response.size());
+ MsgPack::bin_t<uint8_t> request_id;
+ MsgPack::bin_t<uint8_t> response_data;
+ unpacker.from_array(request_id, response_data);
+
+ handle_response(request_id, response_data, resource.total_size(), resource.size());
+ }
+ else {
+ DEBUGF("Incoming response resource failed with status: %d", resource.status());
+ for (RNS::RequestReceipt pending_request : _object->_pending_requests) {
+ if (pending_request.request_id() == resource.request_id()) {
+ pending_request.request_timed_out({Type::NONE});
+ }
+ }
+ }
+}
+
+
+/*z
+"""
+Get the ``Channel`` for this link.
+
+:return: ``Channel`` object
+"""
+void Link::get_channel() {
+ assert(_object);
+ if _object->_channel is None:
+ _object->_channel = Channel(LinkChannelOutlet(self))
+ return _object->_channel
+*/
+
+/*
+void Link::receive(const Packet& packet) {
+}
+*/
+void Link::receive(const Packet& packet) {
+ assert(_object);
+ _object->_watchdog_lock = true;
+ if (_object->_status != Type::Link::CLOSED && !(_object->_initiator && packet.context() == Type::Packet::KEEPALIVE && packet.data() == "\xFF")) {
+ if (packet.receiving_interface() != _object->_attached_interface) {
+ ERROR("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!");
+ }
+ else {
+ _object->_last_inbound = OS::time();
+ if (packet.context() != Type::Packet::KEEPALIVE) {
+ _object->_last_data = _object->_last_inbound;
+ }
+ _object->_rx += 1;
+ _object->_rxbytes += packet.data().size();
+ if (_object->_status == STALE) {
+ _object->_status = Type::Link::ACTIVE;
+ }
+
+ if (packet.packet_type() == Type::Packet::DATA) {
+ bool should_query = false;
+ switch (packet.context()) {
+ case Type::Packet::CONTEXT_NONE:
+ {
+ const Bytes plaintext = decrypt(packet.data());
+ if (plaintext) {
+ if (_object->_callbacks._packet) {
+ //z thread = threading.Thread(target=_object->_callbacks.packet, args=(plaintext, packet))
+ //z thread.daemon = True
+ //z thread.start()
+ try {
+ _object->_callbacks._packet(plaintext, packet);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing packet callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+
+ if (_object->_destination.proof_strategy() == Type::Destination::PROVE_ALL) {
+ const_cast<Packet&>(packet).prove();
+ should_query = true;
+ }
+ else if (_object->_destination.proof_strategy() == Type::Destination::PROVE_APP) {
+ if (_object->_destination.callbacks()._proof_requested) {
+ try {
+ if (_object->_destination.callbacks()._proof_requested(packet)) {
+ const_cast<Packet&>(packet).prove();
+ should_query = true;
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing proof request callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+ }
+ }
+ break;
+ }
+ case Type::Packet::LINKIDENTIFY:
+ {
+ const Bytes plaintext = decrypt(packet.data());
+ if (plaintext) {
+ if (!(_object->_initiator) && plaintext.size() == Type::Identity::KEYSIZE/8 + Type::Identity::SIGLENGTH/8) {
+ const Bytes public_key = plaintext.left(Type::Identity::KEYSIZE/8);
+ const Bytes signed_data = _object->_link_id + public_key;
+ const Bytes signature = plaintext.mid(Type::Identity::KEYSIZE/8, Type::Identity::SIGLENGTH/8);
+ Identity identity(false);
+ identity.load_public_key(public_key);
+
+ if (identity.validate(signature, signed_data)) {
+ _object->__remote_identity = identity;
+ if (_object->_callbacks._remote_identified) {
+ try {
+ _object->_callbacks._remote_identified(*this, _object->__remote_identity);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing remote identified callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ case Type::Packet::REQUEST:
+ {
+ try {
+ const Bytes request_id = packet.getTruncatedHash();
+ const Bytes packed_request = decrypt(packet.data());
+ if (packed_request) {
+ //p unpacked_request = umsgpack.unpackb(packed_request)
+ MsgPack::Unpacker unpacker;
+ unpacker.feed(packed_request.data(), packed_request.size());
+ // CBA TODO OPTIMIZE MSGPACK
+ double requested_at;
+ MsgPack::bin_t<uint8_t> path_hash;
+ MsgPack::bin_t<uint8_t> request_data;
+ unpacker.from_array(requested_at, path_hash, request_data);
+ ResourceRequest resource_request;
+ resource_request._requested_at = requested_at;
+ resource_request._path_hash = path_hash;
+ resource_request._request_data = request_data;
+ handle_request(request_id, resource_request);
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Error occurred while handling request. The contained exception was: %s", e.what());
+ }
+ break;
+ }
+ case Type::Packet::RESPONSE:
+ {
+ try {
+ const Bytes packed_response = decrypt(packet.data());
+ if (packed_response) {
+ //p unpacked_response = umsgpack.unpackb(packed_response)
+ //p request_id = unpacked_response[0]
+ //p response_data = unpacked_response[1]
+ //p transfer_size = len(umsgpack.packb(response_data))-2
+ MsgPack::Unpacker unpacker;
+ unpacker.feed(packed_response.data(), packed_response.size());
+ MsgPack::bin_t<uint8_t> request_id;
+ MsgPack::bin_t<uint8_t> response_data;
+ unpacker.from_array(request_id, response_data);
+ MsgPack::Packer packer;
+ packer.serialize(response_data);
+ size_t transfer_size = packer.size() - 2;
+ handle_response(Bytes(request_id.data(), request_id.size()), Bytes(response_data.data(), response_data.size()), transfer_size, transfer_size);
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Error occurred while handling response. The contained exception was: %s", e.what());
+ }
+ break;
+ }
+ case Type::Packet::LRRTT:
+ {
+ if (!_object->_initiator) {
+ rtt_packet(packet);
+ }
+ }
+ case Type::Packet::LINKCLOSE:
+ {
+ teardown_packet(packet);
+ break;
+ }
+/*z
+ case Type::Packet::RESOURCE_ADV:
+ {
+ //p packet.plaintext = decrypt(packet.data)
+ const Bytes plaintext = decrypt(packet.data());
+ if (plaintext) {
+ const_cast<Packet&>(packet).plaintext(plaintext);
+ if (ResourceAdvertisement::is_request(packet)) {
+ Resource::accept(packet, callback=_object->_request_resource_concluded);
+ }
+ else if (ResourceAdvertisement::is_response(packet)) {
+ Bytes request_id = ResourceAdvertisement::read_request_id(packet)
+ for (auto& pending_request : _object->_pending_requests) {
+ if (pending_request.request_id == request_id) {
+ const Bytes response_resource = Resource::accept(packet, callback=_object->_response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id);
+ if (response_resource) {
+ //p if pending_request.response_size == None:
+ if (pending_request.response_size == 0) {
+ pending_request.response_size = ResourceAdvertisement::read_size(packet);
+ }
+ //p if pending_request.response_transfer_size == None:
+ if (pending_request.response_transfer_size == 0) {
+ pending_request.response_transfer_size = 0;
+ }
+ pending_request.response_transfer_size += ResourceAdvertisement::read_transfer_size(packet);
+ //p if pending_request.started_at == None:
+ if (pending_request.started_at == 0.0) {
+ pending_request.started_at = OS::time();
+ }
+ pending_request.response_resource_progress(response_resource);
+ }
+ }
+ }
+ }
+ else if (_object->_resource_strategy == ACCEPT_NONE) {
+ //p pass
+ }
+ else if (_object->_resource_strategy == ACCEPT_APP) {
+ if (_object->_callbacks.resource) {
+ try {
+ resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext());
+ resource_advertisement.link = *this;
+ if (_object->_callbacks.resource(resource_advertisement)) {
+ Resource::accept(packet, _object->_callbacks.resource_concluded);
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing resource accept callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ elif _object->_resource_strategy == ACCEPT_ALL:
+ RNS.Resource.accept(packet, _object->_callbacks.resource_concluded)
+ break;
+ }
+ case Type::Packet::RESOURCE_REQ:
+ {
+ const Bytes plaintext = decrypt(packet.data());
+ if (plaintext) {
+ if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED:
+ resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:Type::Identity::HASHLENGTH//8+1+RNS.Resource.MAPHASH_LEN]
+ else:
+ resource_hash = plaintext[1:Type::Identity::HASHLENGTH//8+1]
+
+ for resource in _object->_outgoing_resources:
+ if resource.hash == resource_hash:
+ // We need to check that this request has not been
+ // received before in order to avoid sequencing errors.
+ if not packet.packet_hash in resource.req_hashlist:
+ resource.req_hashlist.append(packet.packet_hash)
+ resource.request(plaintext)
+ break;
+ }
+ case Type::Packet::RESOURCE_HMU:
+ {
+ const Bytes plaintext = decrypt(packet.data());
+ if (plaintext) {
+ resource_hash = plaintext[:Type::Identity::HASHLENGTH//8]
+ for resource in _object->_incoming_resources:
+ if resource_hash == resource.hash:
+ resource.hashmap_update_packet(plaintext)
+ break;
+ }
+ case Type::Packet::RESOURCE_ICL:
+ {
+ const Bytes plaintext = decrypt(packet.data());
+ if (plaintext) {
+ resource_hash = plaintext[:Type::Identity::HASHLENGTH//8]
+ for resource in _object->_incoming_resources:
+ if resource_hash == resource.hash:
+ resource.cancel()
+ break;
+ }
+*/
+ case Type::Packet::KEEPALIVE:
+ {
+ if (!_object->_initiator && packet.data() == "\xFF") {
+ //p keepalive_packet = RNS.Packet(self, bytes([0xFE]), context=RNS.Packet.KEEPALIVE)
+ Packet keepalive_packet(*this, Bytes("\xFE"), Type::Packet::DATA, Type::Packet::KEEPALIVE);
+ keepalive_packet.send();
+ had_outbound(true);
+ }
+ break;
+ }
+ // TODO: find the most efficient way to allow multiple
+ // transfers at the same time, sending resource hash on
+ // each packet is a huge overhead. Probably some kind
+ // of hash -> sequence map
+ case Type::Packet::RESOURCE:
+ {
+ for (auto& resource : _object->_incoming_resources) {
+ //z resource.receive_part(packet);
+ }
+ break;
+ }
+/*z
+ case Type::Packet::CHANNEL:
+ {
+ //z if (!_object->_channel) {
+ if (true) {
+ DEBUG(f"Channel data received without open channel")
+ }
+ else {
+ //z packet.prove();
+ //z plaintext = decrypt(packet.data());
+ //z if (plaintext) {
+ //z _object->_channel._receive(plaintext);
+ //z }
+ }
+ break;
+ }
+*/
+ }
+ }
+ else if (packet.packet_type() == Type::Packet::PROOF) {
+ if (packet.context() == Type::Packet::RESOURCE_PRF) {
+ Bytes resource_hash = packet.data().left(Type::Identity::HASHLENGTH/8);
+ for (const auto& resource : _object->_outgoing_resources) {
+ if (resource_hash == resource.hash()) {
+ //z resource.validate_proof(packet.data());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ _object->_watchdog_lock = false;
+}
+
+const Bytes Link::encrypt(const Bytes& plaintext) {
+ assert(_object);
+ TRACE("Link::encrypt: encrypting data...");
+ try {
+ if (!_object->_token) {
+ try {
+ _object->_token.reset(new Token(_object->_derived_key));
+ }
+ catch (std::exception& e) {
+ ERRORF("Could not instantiate Token while performing encryption on link %s. The contained exception was: %s", toString().c_str(), e.what());
+ throw e;
+ }
+ }
+ return _object->_token->encrypt(plaintext);
+ }
+ catch (std::exception& e) {
+ ERRORF("Encryption on link %s failed. The contained exception was: %s", toString().c_str(), e.what());
+ throw e;
+ }
+}
+
+const Bytes Link::decrypt(const Bytes& ciphertext) {
+ assert(_object);
+ TRACE("Link::decrypt: decrypting data...");
+ try {
+ if (!_object->_token) {
+ _object->_token.reset(new Token(_object->_derived_key));
+ }
+ return _object->_token->decrypt(ciphertext);
+ }
+ catch (std::exception& e) {
+ ERRORF("Decryption failed on link %s. The contained exception was: %s", toString().c_str(), e.what());
+ return {Bytes::NONE};
+ }
+}
+
+const Bytes Link::sign(const Bytes& message) {
+ assert(_object);
+ return _object->_sig_prv->sign(message);
+}
+
+bool Link::validate(const Bytes& signature, const Bytes& message) {
+ assert(_object);
+ try {
+ _object->_peer_sig_pub->verify(signature, message);
+ return true;
+ }
+ catch (std::exception& e) {
+ return false;
+ }
+}
+
+void Link::set_link_established_callback(Callbacks::established callback) {
+ assert(_object);
+ _object->_callbacks._established = callback;
+}
+
+void Link::set_link_closed_callback(Callbacks::closed callback) {
+ assert(_object);
+ _object->_callbacks._closed = callback;
+}
+
+void Link::set_packet_callback(Callbacks::packet callback) {
+ assert(_object);
+ _object->_callbacks._packet = callback;
+}
+
+void Link::set_remote_identified_callback(Callbacks::remote_identified callback) {
+ assert(_object);
+ _object->_callbacks._remote_identified = callback;
+}
+
+void Link::set_resource_callback(Callbacks::resource callback) {
+ assert(_object);
+ _object->_callbacks._resource = callback;
+}
+
+void Link::set_resource_started_callback(Callbacks::resource_started callback) {
+ assert(_object);
+ _object->_callbacks._resource_started = callback;
+}
+
+void Link::set_resource_concluded_callback(Callbacks::resource_concluded callback) {
+ assert(_object);
+ _object->_callbacks._resource_concluded = callback;
+}
+
+
+void Link::resource_concluded(const Resource& resource) {
+ assert(_object);
+ if (_object->_incoming_resources.count(resource) > 0) {
+ _object->_incoming_resources.erase(resource);
+ }
+ if (_object->_outgoing_resources.count(resource) > 0) {
+ _object->_outgoing_resources.erase(resource);
+ }
+}
+
+/*
+Sets the resource strategy for the link.
+
+:param resource_strategy: One of ``RNS.ACCEPT_NONE``, ``RNS.ACCEPT_ALL`` or ``RNS.ACCEPT_APP``. If ``RNS.ACCEPT_APP`` is set, the `resource_callback` will be called to determine whether the resource should be accepted or not.
+:raises: *TypeError* if the resource strategy is unsupported.
+*/
+void Link::set_resource_strategy(resource_strategy strategy) {
+ assert(_object);
+ if (!(strategy & resource_strategies)) {
+ throw std::runtime_error("Unsupported resource strategy");
+ }
+ else {
+ _object->_resource_strategy = strategy;
+ }
+}
+
+void Link::register_outgoing_resource(const Resource& resource) {
+ assert(_object);
+ _object->_outgoing_resources.insert(resource);
+}
+
+void Link::register_incoming_resource(const Resource& resource) {
+ assert(_object);
+ _object->_incoming_resources.insert(resource);
+}
+
+bool Link::has_incoming_resource(const Resource& resource) {
+ assert(_object);
+ for (const auto& incoming_resource : _object->_incoming_resources) {
+ if (incoming_resource.hash() == resource.hash()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Link::cancel_outgoing_resource(const Resource& resource) {
+ assert(_object);
+ if (_object->_outgoing_resources.count(resource) > 0) {
+ _object->_outgoing_resources.erase(resource);
+ }
+ else {
+ ERROR("Attempt to cancel a non-existing outgoing resource");
+ }
+}
+
+void Link::cancel_incoming_resource(const Resource& resource) {
+ assert(_object);
+ if (_object->_incoming_resources.count(resource)) {
+ _object->_incoming_resources.erase(resource);
+ }
+ else {
+ ERROR("Attempt to cancel a non-existing incoming resource");
+ }
+}
+
+bool Link::ready_for_new_resource() {
+ assert(_object);
+ return (_object->_outgoing_resources.size() > 0);
+}
+
+std::string Link::toString() const {
+ if (!_object) {
+ return "";
+ }
+ return "{Link:" + _object->_link_id.toHex() + "}";
+}
+
+// getters
+
+double Link::rtt() const {
+ assert(_object);
+ return _object->_rtt;
+}
+
+const Destination& Link::destination() const {
+ assert(_object);
+ return _object->_destination;
+}
+
+// CBA LINK
+/*
+const Destination& Link::link_destination() const {
+ assert(_object);
+ return _object->_link_destination;
+}
+*/
+
+const Bytes& Link::link_id() const {
+ assert(_object);
+ return _object->_link_id;
+}
+
+const Bytes& Link::hash() const {
+ assert(_object);
+ return _object->_hash;
+}
+
+uint16_t Link::mtu() const {
+ assert(_object);
+ return _object->_mtu;
+}
+
+Type::Link::status Link::status() const {
+ assert(_object);
+ return _object->_status;
+}
+
+double Link::establishment_timeout() const {
+ assert(_object);
+ return _object->_establishment_timeout;
+}
+
+uint16_t Link::establishment_cost() const {
+ assert(_object);
+ return _object->_establishment_cost;
+}
+
+uint8_t Link::traffic_timeout_factor() const {
+ assert(_object);
+ return _object->_traffic_timeout_factor;
+}
+
+double Link::request_time() const {
+ assert(_object);
+ return _object->_request_time;
+}
+
+double Link::last_inbound() const {
+ assert(_object);
+ return _object->_last_inbound;
+}
+
+std::set<RNS::RequestReceipt>& Link::pending_requests() const {
+ assert(_object);
+ return _object->_pending_requests;
+}
+
+Type::Link::teardown_reason Link::teardown_reason() const {
+ assert(_object);
+ return _object->_teardown_reason;
+}
+
+bool Link::initiator() const {
+ assert(_object);
+ return _object->_initiator;
+}
+
+// setters
+
+void Link::destination(const Destination& destination) {
+ assert(_object);
+ _object->_destination = destination;
+}
+
+void Link::attached_interface(const Interface& interface) {
+ assert(_object);
+ _object->_attached_interface = interface;
+}
+
+void Link::establishment_timeout(double timeout) {
+ assert(_object);
+ _object->_establishment_timeout = timeout;
+}
+
+void Link::establishment_cost(uint16_t cost) {
+ assert(_object);
+ _object->_establishment_cost = cost;
+}
+
+void Link::request_time(double time) {
+ assert(_object);
+ _object->_request_time = time;
+}
+
+void Link::last_inbound(double time) {
+ assert(_object);
+ _object->_last_inbound = time;
+}
+
+void Link::last_outbound(double time) {
+ assert(_object);
+ _object->_last_outbound = time;
+}
+
+void Link::increment_tx() {
+ assert(_object);
+ _object->_tx++;
+}
+
+void Link::increment_txbytes(uint16_t bytes) {
+ assert(_object);
+ _object->_txbytes += bytes;
+}
+
+void Link::status(Type::Link::status status) {
+ assert(_object);
+ _object->_status = status;
+}
+
+void Link::mtu(uint16_t mtu) {
+ assert(_object);
+ _object->_mtu = mtu;
+}
+
+void Link::mode(RNS::Type::Link::link_mode mode) {
+ assert(_object);
+ _object->_mode = mode;
+}
+
+
+//RequestReceipt::RequestReceipt(const Link& link, const PacketReceipt& packet_receipt /*= {Type::NONE}*/, const Resource& resource /*= {Type::NONE}*/, RequestReceipt::Callbacks::response response_callback /*= nullptr*/, RequestReceipt::Callbacks::failed failed_callback /*= nullptr*/, RequestReceipt::Callbacks::progress progress_callback /*= nullptr*/, double timeout /*= 0.0*/, int request_size /*= 0*/) :
+RequestReceipt::RequestReceipt(const Link& link, const PacketReceipt& packet_receipt, const Resource& resource, RequestReceipt::Callbacks::response response_callback /*= nullptr*/, RequestReceipt::Callbacks::failed failed_callback /*= nullptr*/, RequestReceipt::Callbacks::progress progress_callback /*= nullptr*/, double timeout /*= 0.0*/, int request_size /*= 0*/) :
+ _object(new RequestReceiptData())
+{
+ assert(_object);
+ _object->_packet_receipt = packet_receipt;
+ _object->_resource = resource;
+ if (_object->_packet_receipt) {
+ _object->_hash = packet_receipt.truncated_hash();
+ //z _object->_packet_receipt.set_timeout_callback(request_timed_out);
+ _object->_started_at = OS::time();
+ }
+ else if (_object->_resource) {
+ _object->_hash = resource.request_id();
+ // CBA TODO Need to find cross-platform way of passing class method as callback
+ //z const_cast<Resource&>(resource).set_concluded_callback(request_resource_concluded);
+ //z const_cast<Resource&>(resource).set_concluded_callback(std::bind(&RequestReceipt::request_resource_concluded, this, std::placeholders::_1));
+ }
+ _object->_link = link;
+ _object->_request_id = _object->_hash;
+ _object->_request_size = request_size;
+ _object->_sent_at = OS::time();
+ if (timeout != 0.0) {
+ _object->_timeout = timeout;
+ }
+ else {
+ throw new std::invalid_argument("No timeout specified for request receipt");
+ }
+
+ _object->_callbacks._response = response_callback;
+ _object->_callbacks._failed = failed_callback;
+ _object->_callbacks._progress = progress_callback;
+
+ _object->_link.pending_requests().insert(*this);
+}
+
+void RequestReceipt::request_resource_concluded(const Resource& resource) {
+ assert(_object);
+ if (resource.status() == Type::Resource::COMPLETE) {
+ DEBUGF("Request %s successfully sent as resource.", _object->_request_id.toHex().c_str());
+ if (_object->_started_at == 0.0) {
+ _object->_started_at = OS::time();
+ }
+ _object->_status = Type::RequestReceipt::DELIVERED;
+ _object->_resource_response_timeout = OS::time() + _object->_timeout;
+ //p response_timeout_thread = threading.Thread(target=_object->___response_timeout_job)
+ //p response_timeout_thread.daemon = True
+ //p response_timeout_thread.start()
+ }
+ else {
+ DEBUGF("Sending request %s as resource failed with status: %d", _object->_request_id.toHex().c_str(), resource.status());
+ _object->_status = Type::RequestReceipt::FAILED;
+ _object->_concluded_at = OS::time();
+ _object->_link.pending_requests().erase(*this);
+ if (_object->_callbacks._failed != nullptr) {
+ try {
+ _object->_callbacks._failed(*this);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing request failed callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+ }
+}
+
+
+void RequestReceipt::__response_timeout_job() {
+ assert(_object);
+ while (_object->_status == Type::RequestReceipt::DELIVERED) {
+ double now = OS::time();
+ if (now > _object->___resource_response_timeout) {
+ request_timed_out({Type::NONE});
+ }
+
+ OS::sleep(0.1);
+ }
+}
+
+
+void RequestReceipt::request_timed_out(const PacketReceipt& packet_receipt) {
+ assert(_object);
+ _object->_status = Type::RequestReceipt::FAILED;
+ _object->_concluded_at = OS::time();
+ _object->_link.pending_requests().erase(*this);
+
+ if (_object->_callbacks._failed != nullptr) {
+ try {
+ _object->_callbacks._failed(*this);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing request timed out callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+}
+
+
+void RequestReceipt::response_resource_progress(const Resource& resource) {
+ assert(_object);
+ if (resource) {
+ if (_object->_status != Type::RequestReceipt::FAILED) {
+ _object->_status = Type::RequestReceipt::RECEIVING;
+ if (_object->_packet_receipt) {
+ if (_object->_packet_receipt.status() != Type::PacketReceipt::DELIVERED) {
+ _object->_packet_receipt.status(Type::PacketReceipt::DELIVERED);
+ _object->_packet_receipt.proved(true);
+ _object->_packet_receipt.concluded_at(OS::time());
+ if (_object->_packet_receipt.callbacks()._delivery != nullptr) {
+ _object->_packet_receipt.callbacks()._delivery(_object->_packet_receipt);
+ }
+ }
+ }
+
+ _object->_progress = resource.get_progress();
+
+ if (_object->_callbacks._progress != nullptr) {
+ try {
+ _object->_callbacks._progress(*this);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing response progress callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+ }
+ else {
+ const_cast<Resource&>(resource).cancel();
+ }
+ }
+}
+
+void RequestReceipt::response_received(const Bytes& response) {
+ assert(_object);
+ if (_object->_status != Type::RequestReceipt::FAILED) {
+ _object->_progress = 1.0;
+ _object->_response = response;
+ _object->_status = Type::RequestReceipt::READY;
+ _object->_response_concluded_at = OS::time();
+
+ if (_object->_packet_receipt) {
+ _object->_packet_receipt.status(Type::PacketReceipt::DELIVERED);
+ _object->_packet_receipt.proved(true);
+ _object->_packet_receipt.concluded_at(OS::time());
+ if (_object->_packet_receipt.callbacks()._delivery != nullptr) {
+ _object->_packet_receipt.callbacks()._delivery(_object->_packet_receipt);
+ }
+ }
+ if (_object->_callbacks._progress != nullptr) {
+ try {
+ _object->_callbacks._progress(*this);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing response progress callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+ if (_object->_callbacks._response != nullptr) {
+ try {
+ _object->_callbacks._response(*this);
+ }
+ catch (std::exception& e) {
+ ERRORF("Error while executing response received callback from %s. The contained exception was: %s", toString().c_str(), e.what());
+ }
+ }
+ }
+}
+
+// :returns: The request ID as *bytes*.
+const Bytes& RequestReceipt::get_request_id() const {
+ assert(_object);
+ return _object->_request_id;
+}
+
+// :returns: The current status of the request, one of ``RNS.RequestReceipt.FAILED``, ``RNS.RequestReceipt.SENT``, ``RNS.RequestReceipt.DELIVERED``, ``RNS.RequestReceipt.READY``.
+Type::RequestReceipt::status RequestReceipt::get_status() const {
+ assert(_object);
+ return _object->_status;
+}
+
+// :returns: The progress of a response being received as a *float* between 0.0 and 1.0.
+float RequestReceipt::get_progress() const {
+ assert(_object);
+ return _object->_progress;
+}
+
+// :returns: The response as *bytes* if it is ready, otherwise *None*.
+const Bytes RequestReceipt::get_response() const {
+ assert(_object);
+ if (_object->_status == Type::RequestReceipt::READY) {
+ return _object->_response;
+ }
+ else {
+ return Bytes::NONE;
+ }
+}
+
+// :returns: The response time of the request in seconds.
+double RequestReceipt::get_response_time() const {
+ assert(_object);
+ if (_object->_status == Type::RequestReceipt::READY) {
+ return _object->_response_concluded_at - _object->_started_at;
+ }
+ else {
+ return 0.0;
+ }
+}
+
+std::string RequestReceipt::toString() const {
+ if (!_object) {
+ return "";
+ }
+ return "{RequestReceipt:" + _object->_hash.toHex() + "}";
+}
+
+// getters
+
+const Bytes& RequestReceipt::hash() const {
+ assert(_object);
+ return _object->_hash;
+}
+
+const Bytes& RequestReceipt::request_id() const {
+ assert(_object);
+ return _object->_request_id;
+}
+
+size_t RequestReceipt::response_transfer_size() const {
+ assert(_object);
+ return _object->_response_transfer_size;
+}
+
+// setters
+
+void RequestReceipt::response_size(size_t size) {
+ assert(_object);
+ _object->_response_size = size;
+}
+
+void RequestReceipt::response_transfer_size(size_t size) {
+ assert(_object);
+ _object->_response_transfer_size = size;
+}

diff --git a/lib/microReticulum/src/Link.h b/lib/microReticulum/src/Link.h
new file mode 100755
index 0000000..8f02fe6
--- /dev/null
+++ b/lib/microReticulum/src/Link.h
@@ -0,0 +1,270 @@
+#pragma once
+
+#include "Destination.h"
+#include "Type.h"
+
+#include <memory>
+#include <cassert>
+
+namespace RNS {
+
+ class ResourceRequest;
+ class ResourceResponse;
+ class RequestReceipt;
+ class Link;
+
+ class LinkData;
+ class RequestReceiptData;
+ class Resource;
+ class Packet;
+ class Destination;
+ class ResourceAdvertisement;
+ class PacketReceipt;
+
+ class ResourceRequest {
+ public:
+ double _requested_at = 0.0;
+ Bytes _path_hash;
+ Bytes _request_data;
+ };
+
+ class ResourceResponse {
+ Bytes request_id;
+ Bytes response_data;
+ };
+
+/*
+ An instance of this class is returned by the ``request`` method of ``RNS.Link``
+ instances. It should never be instantiated manually. It provides methods to
+ check status, response time and response data when the request concludes.
+*/
+ class RequestReceipt {
+
+ public:
+ class Callbacks {
+ public:
+ using response = void(*)(const RequestReceipt& packet_receipt);
+ using failed = void(*)(const RequestReceipt& packet_receipt);
+ using progress = void(*)(const RequestReceipt& packet_receipt);
+ public:
+ response _response = nullptr;
+ failed _failed = nullptr;
+ progress _progress = nullptr;
+ friend class RequestReceipt;
+ };
+
+ public:
+ RequestReceipt(Type::NoneConstructor none) {}
+ RequestReceipt(const RequestReceipt& request_receipt) : _object(request_receipt._object) {}
+ //RequestReceipt(const Link& link, const PacketReceipt& packet_receipt = {Type::NONE}, const Resource& resource = {Type::NONE}, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int request_size = 0);
+ RequestReceipt(const Link& link, const PacketReceipt& packet_receipt, const Resource& resource, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int request_size = 0);
+
+ inline RequestReceipt& operator = (const RequestReceipt& packet_receipt) {
+ _object = packet_receipt._object;
+ return *this;
+ }
+ inline operator bool() const {
+ return _object.get() != nullptr;
+ }
+ inline bool operator < (const RequestReceipt& packet_receipt) const {
+ return _object.get() < packet_receipt._object.get();
+ }
+
+ public:
+ void request_resource_concluded(const Resource& resource);
+ void __response_timeout_job();
+ void request_timed_out(const PacketReceipt& packet_receipt);
+ void response_resource_progress(const Resource& resource);
+ void response_received(const Bytes& response);
+ const Bytes& get_request_id() const;
+ Type::RequestReceipt::status get_status() const;
+ float get_progress() const;
+ const Bytes get_response() const;
+ double get_response_time() const;
+
+ std::string toString() const;
+
+ // getters
+ const Bytes& hash() const;
+ const Bytes& request_id() const;
+ size_t response_transfer_size() const;
+
+ // setters
+ void response_size(size_t size);
+ void response_transfer_size(size_t size);
+
+ private:
+ std::shared_ptr<RequestReceiptData> _object;
+
+ };
+
+/*
+ This class is used to establish and manage links to other peers. When a
+ link instance is created, Reticulum will attempt to establish verified
+ and encrypted connectivity with the specified destination.
+
+ :param destination: A :ref:`RNS.Destination<api-destination>` instance which to establish a link to.
+ :param established_callback: An optional function or method with the signature *callback(link)* to be called when the link has been established.
+ :param closed_callback: An optional function or method with the signature *callback(link)* to be called when the link is closed.
+*/
+ class Link {
+
+ public:
+ class Callbacks {
+ public:
+ using established = void(*)(Link& link);
+ using closed = void(*)(Link& link);
+ using packet = void(*)(const Bytes& plaintext, const Packet& packet);
+ using remote_identified = void(*)(const Link& link, const Identity& remote_identity);
+ using resource = void(*)(const ResourceAdvertisement& resource_advertisement);
+ using resource_started = void(*)(const Resource& resource);
+ using resource_concluded = void(*)(const Resource& resource);
+ public:
+ established _established = nullptr;
+ closed _closed = nullptr;
+ packet _packet = nullptr;
+ remote_identified _remote_identified = nullptr;
+ resource _resource = nullptr;
+ resource_started _resource_started = nullptr;
+ resource_concluded _resource_concluded = nullptr;
+ friend class Link;
+ };
+
+ public:
+ static uint8_t resource_strategies;
+ static std::set<RNS::Type::Link::link_mode> ENABLED_MODES;
+ static RNS::Type::Link::link_mode MODE_DEFAULT;
+
+ public:
+ Link(Type::NoneConstructor none) {
+ MEM("Link NONE object created");
+ }
+ Link(const Link& link) : _object(link._object) {
+ MEM("Link object copy created");
+ }
+ Link(const Destination& destination = {Type::NONE}, Callbacks::established established_callback = nullptr, Callbacks::closed closed_callback = nullptr, const Destination& owner = {Type::NONE}, const Bytes& peer_pub_bytes = {Bytes::NONE}, const Bytes& peer_sig_pub_bytes = {Bytes::NONE}, RNS::Type::Link::link_mode mode = MODE_DEFAULT);
+ //Link(const Destination& destination = {Type::NONE}, Callbacks::established established_callback = nullptr, Callbacks::closed closed_callback = nullptr, const Destination& owner = {Type::NONE}, const Bytes& peer_pub_bytes = {Bytes::NONE}, const Bytes& peer_sig_pub_bytes = {Bytes::NONE}, RNS::Type::Link::link_mode mode = MODE_DEFAULT);
+ virtual ~Link(){
+ MEM("Link object destroyed");
+ }
+
+ Link& operator = (const Link& link) {
+ _object = link._object;
+ return *this;
+ }
+ operator bool() const {
+ return _object.get() != nullptr;
+ }
+ bool operator < (const Link& link) const {
+ return _object.get() < link._object.get();
+ }
+
+ public:
+ static Bytes signalling_bytes(uint16_t mtu, RNS::Type::Link::link_mode mode);
+ static uint16_t mtu_from_lr_packet(const Packet& packet);
+ static uint16_t mtu_from_lp_packet(const Packet& packet);
+ static uint8_t mode_byte(RNS::Type::Link::link_mode mode);
+ static RNS::Type::Link::link_mode mode_from_lr_packet(const Packet& packet);
+ static RNS::Type::Link::link_mode mode_from_lp_packet(const Packet& packet);
+ static Bytes link_id_from_lr_packet(const Packet& packet);
+ static Link validate_request( const Destination& owner, const Bytes& data, const Packet& packet);
+
+ public:
+ void load_peer(const Bytes& peer_pub_bytes, const Bytes& peer_sig_pub_bytes);
+ void set_link_id(const Packet& packet);
+ void handshake();
+ void prove();
+ void prove_packet(const Packet& packet);
+ void validate_proof(const Packet& packet);
+ void identify(const Identity& identity);
+ const RequestReceipt request(const Bytes& path, const Bytes& data = {Bytes::NONE}, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0);
+ void update_mdu();
+ void rtt_packet(const Packet& packet);
+ float get_establishment_rate();
+ uint16_t get_mtu();
+ uint16_t get_mdu();
+ float get_expected_rate();
+ RNS::Type::Link::link_mode get_mode();
+ const Bytes& get_salt();
+ const Bytes get_context();
+ double get_age();
+ double no_inbound_for();
+ double no_outbound_for();
+ double no_data_for();
+ double inactive_for();
+ const Identity& get_remote_identity();
+ void had_outbound(bool is_keepalive = false);
+ void teardown();
+ void teardown_packet(const Packet& packet);
+ void link_closed();
+ void start_watchdog();
+ void __watchdog_job();
+ void send_keepalive();
+ void handle_request(const Bytes& request_id, const ResourceRequest& unpacked_request);
+ void handle_response(const Bytes& request_id, const Bytes& response_data, size_t response_size, size_t response_transfer_size);
+ void request_resource_concluded(const Resource& resource);
+ void response_resource_concluded(const Resource& resource);
+ //z const Channel& get_channel();
+ void receive(const Packet& packet);
+ const Bytes encrypt(const Bytes& plaintext);
+ const Bytes decrypt(const Bytes& ciphertext);
+ const Bytes sign(const Bytes& message);
+ bool validate(const Bytes& signature, const Bytes& message);
+ void set_link_established_callback(Callbacks::established callback);
+ void set_link_closed_callback(Callbacks::closed callback);
+ void set_packet_callback(Callbacks::packet callback);
+ void set_remote_identified_callback(Callbacks::remote_identified callback);
+ void set_resource_callback(Callbacks::resource callback);
+ void set_resource_started_callback(Callbacks::resource_started callback);
+ void set_resource_concluded_callback(Callbacks::resource_concluded callback);
+ void resource_concluded(const Resource& resource);
+ void set_resource_strategy(Type::Link::resource_strategy strategy);
+ void register_outgoing_resource(const Resource& resource);
+ void register_incoming_resource(const Resource& resource);
+ bool has_incoming_resource(const Resource& resource);
+ void cancel_outgoing_resource(const Resource& resource);
+ void cancel_incoming_resource(const Resource& resource);
+ bool ready_for_new_resource();
+
+ //void __str__();
+ std::string toString() const;
+
+ // getters
+ double rtt() const;
+ const Destination& destination() const;
+ // CBA LINK
+ //const Destination& link_destination() const;
+ const Interface& attached_interface() const;
+ const Bytes& link_id() const;
+ const Bytes& hash() const;
+ uint16_t mtu() const;
+ Type::Link::status status() const;
+ double establishment_timeout() const;
+ uint16_t establishment_cost() const;
+ uint8_t traffic_timeout_factor() const;
+ double request_time() const;
+ double last_inbound() const;
+ std::set<RequestReceipt>& pending_requests() const;
+ Type::Link::teardown_reason teardown_reason() const;
+ bool initiator() const;
+
+ // setters
+ void destination(const Destination& destination);
+ void attached_interface(const Interface& interface);
+ void establishment_timeout(double timeout);
+ void establishment_cost(uint16_t cost);
+ void request_time(double time);
+ void last_inbound(double time);
+ void last_outbound(double time);
+ void increment_tx();
+ void increment_txbytes(uint16_t bytes);
+ void status(Type::Link::status status);
+ void mtu(uint16_t mtu);
+ void mode(RNS::Type::Link::link_mode mode);
+
+ protected:
+ std::shared_ptr<LinkData> _object;
+
+ };
+
+}

diff --git a/lib/microReticulum/src/LinkData.h b/lib/microReticulum/src/LinkData.h
new file mode 100755
index 0000000..18d7ddb
--- /dev/null
+++ b/lib/microReticulum/src/LinkData.h
@@ -0,0 +1,135 @@
+#pragma once
+
+//#include "LinkCallbacks.h"
+#include "Link.h"
+
+#include "Resource.h"
+#include "Channel.h"
+#include "Interface.h"
+#include "Packet.h"
+#include "Destination.h"
+#include "Bytes.h"
+#include "Type.h"
+#include "Cryptography/Token.h"
+
+#include <set>
+
+namespace RNS {
+
+ class LinkData {
+ public:
+ LinkData(const Destination& destination) : _destination(destination) {
+ MEM("LinkData object copy created");
+ }
+ virtual ~LinkData() {
+ MEM("LinkData object destroyed");
+ }
+ private:
+ Destination _destination;
+
+ // CBA LINK
+ //Destination _link_destination = {Type::NONE};
+
+ Bytes _link_id;
+ Bytes _hash;
+ Type::Link::status _status = Type::Link::PENDING;
+
+ RNS::Type::Link::link_mode _mode = Link::MODE_DEFAULT;
+ double _rtt = 0.0;
+ uint16_t _mtu = RNS::Type::Reticulum::MTU;
+ uint16_t _mdu = 0;
+ uint16_t _establishment_cost = 0;
+ Link::Callbacks _callbacks;
+ Type::Link::resource_strategy _resource_strategy = Type::Link::ACCEPT_NONE;
+ double _last_inbound = 0.0;
+ double _last_outbound = 0.0;
+ double _last_keepalive = 0.0;
+ double _last_proof = 0.0;
+ double _last_data = 0.0;
+ uint16_t _tx = 0;
+ uint16_t _rx = 0;
+ uint16_t _txbytes = 0;
+ uint16_t _rxbytes = 0;
+ float _rssi = 0.0;
+ float _snr = 0.0;
+ float _q = 0.0;
+ uint8_t _traffic_timeout_factor = Type::Link::TRAFFIC_TIMEOUT_FACTOR;
+ uint16_t _keepalive_timeout_factor = Type::Link::KEEPALIVE_TIMEOUT_FACTOR;
+ uint16_t _keepalive = Type::Link::KEEPALIVE;
+ uint16_t _stale_time = Type::Link::STALE_TIME;
+ bool _watchdog_lock = false;
+ double _activated_at = 0.0;
+ // CBA LINK
+ //Type::Destination::types _type = Type::Destination::LINK;
+ Destination _owner = {Type::NONE};
+ bool _initiator = false;
+ uint8_t _expected_hops = 0;
+ Interface _attached_interface = {Type::NONE};
+ Identity __remote_identity = {Type::NONE};
+ Channel _channel = {Type::NONE};
+ double _establishment_timeout = 0.0;
+ Bytes _request_data;
+ Packet _packet = {Type::NONE};
+ double _request_time = 0.0;
+ float _establishment_rate = 0.0;
+ float _expected_rate = 0.0;
+ Type::Link::teardown_reason _teardown_reason = Type::Link::TEARDOWN_NONE;
+
+ Cryptography::Token::Ptr _token;
+
+ Cryptography::X25519PrivateKey::Ptr _prv;
+ Bytes _prv_bytes;
+
+ Cryptography::Ed25519PrivateKey::Ptr _sig_prv;
+ Bytes _sig_prv_bytes;
+
+ Cryptography::X25519PublicKey::Ptr _pub;
+ Bytes _pub_bytes;
+
+ Cryptography::Ed25519PublicKey::Ptr _sig_pub;
+ Bytes _sig_pub_bytes;
+
+ Cryptography::X25519PublicKey::Ptr _peer_pub;
+ Bytes _peer_pub_bytes;
+
+ Cryptography::Ed25519PublicKey::Ptr _peer_sig_pub;
+ Bytes _peer_sig_pub_bytes;
+
+ Bytes _shared_key;
+ Bytes _derived_key;
+
+ std::set<Resource> _incoming_resources;
+ std::set<Resource> _outgoing_resources;
+ std::set<RNS::RequestReceipt> _pending_requests;
+
+ friend class Link;
+ };
+
+ class RequestReceiptData {
+ public:
+ RequestReceiptData() {}
+ virtual ~RequestReceiptData() {}
+ private:
+ Bytes _hash;
+ PacketReceipt _packet_receipt = {Type::NONE};
+ Resource _resource = {Type::NONE};
+ Link _link;
+ double _started_at = 0.0;
+ Bytes _request_id;
+ int _request_size = 0;
+ Bytes _response;
+ size_t _response_transfer_size = 0;
+ size_t _response_size = 0;
+ Type::RequestReceipt::status _status = Type::RequestReceipt::SENT;
+ double _sent_at = 0.0;
+ int _progress = 0;
+ double _concluded_at = 0.0;
+ double _response_concluded_at = 0.0;
+ double _timeout = 0.0;
+ double _resource_response_timeout = 0.0;
+ double ___resource_response_timeout = 0.0;
+ RequestReceipt::Callbacks _callbacks;
+ friend class RequestReceipt;
+ };
+
+}

diff --git a/lib/microReticulum/src/Log.cpp b/lib/microReticulum/src/Log.cpp
new file mode 100755
index 0000000..2da9a51
--- /dev/null
+++ b/lib/microReticulum/src/Log.cpp
@@ -0,0 +1,111 @@
+#include "Log.h"
+
+#include "Utilities/OS.h"
+
+#include <sys/time.h>
+#include <time.h>
+#include <stdio.h>
+
+using namespace RNS;
+
+//LogLevel _level = LOG_VERBOSE;
+LogLevel _level = LOG_TRACE;
+//LogLevel _level = LOG_MEM;
+RNS::log_callback _on_log = nullptr;
+char _datetime[20];
+
+const char* RNS::getLevelName(LogLevel level) {
+ switch (level) {
+ case LOG_CRITICAL:
+ return "!!!";
+ case LOG_ERROR:
+ return "ERR";
+ case LOG_WARNING:
+ return "WRN";
+ case LOG_NOTICE:
+ return "NOT";
+ case LOG_INFO:
+ return "INF";
+ case LOG_VERBOSE:
+ return "VRB";
+ case LOG_DEBUG:
+ return "DBG";
+ case LOG_TRACE:
+ return "---";
+ case LOG_MEM:
+ return "...";
+ default:
+ return "";
+ }
+}
+
+const char* RNS::getTimeString() {
+#ifdef ARDUINO
+ uint64_t time = Utilities::OS::ltime();
+ if (time < 86400000) {
+ snprintf(_datetime, sizeof(_datetime), "%02d:%02d:%02d.%03d", (int)(time/3600000), (int)((time/60000)%60), (int)((time/1000)%60), (int)(time%1000));
+ }
+ else {
+ snprintf(_datetime, sizeof(_datetime), "%02d-%02d:%02d:%02d.%03d", (int)(time/86400000), (int)((time/3600000)%24), (int)((time/60000)%60), (int)((time/1000)%60), (int)(time%1000));
+ }
+ return _datetime;
+#else
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ int millis = lrint(tv.tv_usec/1000.0);
+ if (millis >= 1000) {
+ millis -= 1000;
+ tv.tv_sec++;
+ }
+ struct tm* tm = localtime(&tv.tv_sec);
+ size_t len = strftime(_datetime, sizeof(_datetime), "%Y-%m-%d %H:%M:%S", tm);
+ snprintf(_datetime+len, sizeof(_datetime)-len, ".%03d", millis);
+ return _datetime;
+#endif
+}
+
+void RNS::loglevel(LogLevel level) {
+ _level = level;
+}
+
+LogLevel RNS::loglevel() {
+ return _level;
+}
+
+void RNS::setLogCallback(log_callback on_log /*= nullptr*/) {
+ _on_log = on_log;
+}
+
+void RNS::doLog(LogLevel level, const char* msg) {
+ if (level > _level) {
+ return;
+ }
+ if (_on_log != nullptr) {
+ _on_log(msg, level);
+ return;
+ }
+#ifdef ARDUINO
+ if (Serial) {
+ Serial.print(getTimeString());
+ Serial.print(" [");
+ Serial.print(getLevelName(level));
+ Serial.print("] ");
+ Serial.println(msg);
+ Serial.flush();
+ }
+#else
+ printf("%s [%s] %s\n", getTimeString(), getLevelName(level), msg);
+#endif
+}
+
+void HEAD(const char* msg, LogLevel level) {
+ if (level > _level) {
+ return;
+ }
+#ifdef ARDUINO
+ Serial.println("");
+#else
+ printf("\n");
+#endif
+ doLog(level, msg);
+}

diff --git a/lib/microReticulum/src/Log.h b/lib/microReticulum/src/Log.h
new file mode 100755
index 0000000..5caacbb
--- /dev/null
+++ b/lib/microReticulum/src/Log.h
@@ -0,0 +1,124 @@
+#pragma once
+
+#ifdef ARDUINO
+#include <Arduino.h>
+#endif
+
+#include <stdarg.h>
+
+#include <string>
+
+#define LOG(msg, level) (RNS::log(msg, level))
+#define LOGF(level, msg, ...) (RNS::logf(level, msg, __VA_ARGS__))
+#define HEAD(msg, level) (RNS::head(msg, level))
+#define HEADF(level, msg, ...) (RNS::headf(level, msg, __VA_ARGS__))
+#define CRITICAL(msg) (RNS::critical(msg))
+#define CRITICALF(msg, ...) (RNS::criticalf(msg, __VA_ARGS__))
+#define ERROR(msg) (RNS::error(msg))
+#define ERRORF(msg, ...) (RNS::errorf(msg, __VA_ARGS__))
+#define WARNING(msg) (RNS::warning(msg))
+#define WARNINGF(msg, ...) (RNS::warningf(msg, __VA_ARGS__))
+#define NOTICE(msg) (RNS::notice(msg))
+#define NOTICEF(msg, ...) (RNS::noticef(msg, __VA_ARGS__))
+#define INFO(msg) (RNS::info(msg))
+#define INFOF(msg, ...) (RNS::infof(msg, __VA_ARGS__))
+#define VERBOSE(msg) (RNS::verbose(msg))
+#define VERBOSEF(msg, ...) (RNS::verbosef(msg, __VA_ARGS__))
+#ifndef NDEBUG
+ #define DEBUG(msg) (RNS::debug(msg))
+ #define DEBUGF(msg, ...) (RNS::debugf(msg, __VA_ARGS__))
+ #define TRACE(msg) (RNS::trace(msg))
+ #define TRACEF(msg, ...) (RNS::tracef(msg, __VA_ARGS__))
+ #if defined(RNS_MEM_LOG)
+ #define MEM(msg) (RNS::mem(msg))
+ #define MEMF(msg, ...) (RNS::memf(msg, __VA_ARGS__))
+ #else
+ #define MEM(ignore) ((void)0)
+ #define MEMF(...) ((void)0)
+ #endif
+#else
+ #define DEBUG(ignore) ((void)0)
+ #define DEBUGF(...) ((void)0)
+ #define TRACE(...) ((void)0)
+ #define TRACEF(...) ((void)0)
+ #define MEM(ignore) ((void)0)
+ #define MEMF(...) ((void)0)
+#endif
+
+namespace RNS {
+
+ enum LogLevel {
+ LOG_NONE = 0,
+ LOG_CRITICAL = 1,
+ LOG_ERROR = 2,
+ LOG_WARNING = 3,
+ LOG_NOTICE = 4,
+ LOG_INFO = 5,
+ LOG_VERBOSE = 6,
+ LOG_DEBUG = 7,
+ LOG_TRACE = 8,
+ LOG_MEM = 9
+ };
+
+ using log_callback = void(*)(const char* msg, LogLevel level);
+
+ const char* getLevelName(LogLevel level);
+ const char* getTimeString();
+
+ void loglevel(LogLevel level);
+ LogLevel loglevel();
+
+ void setLogCallback(log_callback on_log = nullptr);
+
+ void doLog(LogLevel level, const char* msg);
+ inline void doLog(LogLevel level, const char* msg, va_list vlist) { char buf[1024]; vsnprintf(buf, sizeof(buf), msg, vlist); doLog(level, buf); }
+
+ inline void log(const char* msg, LogLevel level = LOG_NOTICE) { doLog(level, msg); }
+#ifdef ARDUINO
+ inline void log(const String& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
+ inline void log(const StringSumHelper& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
+#endif
+ inline void log(const std::string& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
+ inline void logf(LogLevel level, const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(level, msg, vlist); va_end(vlist); }
+
+ void head(const char* msg, LogLevel level = LOG_NOTICE);
+ inline void head(const std::string& msg, LogLevel level = LOG_NOTICE) { head(msg.c_str(), level); }
+ inline void headf(LogLevel level, const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(level, msg, vlist); va_end(vlist); }
+
+ inline void critical(const char* msg) { doLog(LOG_CRITICAL, msg); }
+ inline void critical(const std::string& msg) { doLog(LOG_CRITICAL, msg.c_str()); }
+ inline void criticalf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_CRITICAL, msg, vlist); va_end(vlist); }
+
+ inline void error(const char* msg) { doLog(LOG_ERROR, msg); }
+ inline void error(const std::string& msg) { doLog(LOG_ERROR, msg.c_str()); }
+ inline void errorf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_ERROR, msg, vlist); va_end(vlist); }
+
+ inline void warning(const char* msg) { doLog(LOG_WARNING, msg); }
+ inline void warning(const std::string& msg) { doLog(LOG_WARNING, msg.c_str()); }
+ inline void warningf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_WARNING, msg, vlist); va_end(vlist); }
+
+ inline void notice(const char* msg) { doLog(LOG_NOTICE, msg); }
+ inline void notice(const std::string& msg) { doLog(LOG_NOTICE, msg.c_str()); }
+ inline void noticef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_NOTICE, msg, vlist); va_end(vlist); }
+
+ inline void info(const char* msg) { doLog(LOG_INFO, msg); }
+ inline void info(const std::string& msg) { doLog(LOG_INFO, msg.c_str()); }
+ inline void infof(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_INFO, msg, vlist); va_end(vlist); }
+
+ inline void verbose(const char* msg) { doLog(LOG_VERBOSE, msg); }
+ inline void verbose(const std::string& msg) { doLog(LOG_VERBOSE, msg.c_str()); }
+ inline void verbosef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_VERBOSE, msg, vlist); va_end(vlist); }
+
+ inline void debug(const char* msg) { doLog(LOG_DEBUG, msg); }
+ inline void debug(const std::string& msg) { doLog(LOG_DEBUG, msg.c_str()); }
+ inline void debugf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_DEBUG, msg, vlist); va_end(vlist); }
+
+ inline void trace(const char* msg) { doLog(LOG_TRACE, msg); }
+ inline void trace(const std::string& msg) { doLog(LOG_TRACE, msg.c_str()); }
+ inline void tracef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_TRACE, msg, vlist); va_end(vlist); }
+
+ inline void mem(const char* msg) { doLog(LOG_MEM, msg); }
+ inline void mem(const std::string& msg) { doLog(LOG_MEM, msg.c_str()); }
+ inline void memf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_MEM, msg, vlist); va_end(vlist); }
+
+}

diff --git a/lib/microReticulum/src/Packet.cpp b/lib/microReticulum/src/Packet.cpp
new file mode 100755
index 0000000..0e68a49
--- /dev/null
+++ b/lib/microReticulum/src/Packet.cpp
@@ -0,0 +1,995 @@
+#include "Packet.h"
+
+#include "Reticulum.h"
+#include "Transport.h"
+#include "Identity.h"
+#include "Log.h"
+#include "Utilities/OS.h"
+
+#include <string.h>
+#include <stdexcept>
+
+using namespace RNS;
+using namespace RNS::Type::PacketReceipt;
+using namespace RNS::Type::Packet;
+using namespace RNS::Utilities;
+
+ProofDestination::ProofDestination(const Packet& packet) : Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::SINGLE, packet.get_hash().left(Type::Reticulum::TRUNCATED_HASHLENGTH/8))
+{
+}
+
+Packet::Packet(const Destination& destination, const Interface& attached_interface, const Bytes& data, types packet_type /*= DATA*/, context_types context /*= CONTEXT_NONE*/, Type::Transport::types transport_type /*= Type::Transport::BROADCAST*/, header_types header_type /*= HEADER_1*/, const Bytes& transport_id /*= {Bytes::NONE}*/, bool create_receipt /*= true*/, context_flags context_flag /*= FLAG_UNSET*/) :
+ _object(new Object(destination, attached_interface))
+{
+
+ if (_object->_destination) {
+ TRACE("Creating packet with destination...");
+ // CBA Should never see empty transport_type
+ //if (transport_type == NONE) {
+ // transport_type = Type::Transport::BROADCAST;
+ //}
+ // following moved to object constructor to avoid extra NONE object
+ //_destination = destination;
+ _object->_header_type = header_type;
+ _object->_packet_type = packet_type;
+ _object->_transport_type = transport_type;
+ _object->_context = context;
+ _object->_context_flag = context_flag;
+
+ _object->_transport_id = transport_id;
+ _object->_data = data;
+ if (_object->_data.size() > MDU) {
+ _object->_truncated = true;
+ _object->_data.resize(MDU);
+ }
+ _object->_flags = get_packed_flags();
+
+ _object->_create_receipt = create_receipt;
+ }
+ else {
+ TRACE("Creating packet without destination...");
+ // CBA NOTE: This variant is for creating a new packet from a received raw buffer
+ _object->_raw = data;
+ _object->_packed = true;
+ _object->_fromPacked = true;
+ _object->_create_receipt = false;
+ }
+
+ MEM("Packet object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+}
+
+// CBA LINK
+Packet::Packet(const Link& link, const Bytes& data, Type::Packet::types packet_type /*= Type::Packet::DATA*/, Type::Packet::context_types context /*= Type::Packet::CONTEXT_NONE*/, context_flags context_flag /*= FLAG_UNSET*/) :
+ //_object(new Object(link))
+ //Packet(link.destination(), data, packet_type, context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, {Bytes::NONE}, true, context_flag)
+ // CBA Must use a destination that targets the Link itself instead of the original destination used to create the link
+ Packet(Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::LINK, link.hash()), data, packet_type, context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, {Bytes::NONE}, true, context_flag)
+{
+ TRACE("Creating packet with link...");
+ _object->_destination_link = link;
+ _object->_MTU = link.mtu();
+ // CBA HACK: Need to re-build packed flags since Link was assigned
+ _object->_flags = get_packed_flags();
+ MEM("Packet link object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+}
+
+
+uint8_t Packet::get_packed_flags() {
+ assert(_object);
+ uint8_t packed_flags = 0;
+ if (_object->_context == LRPROOF) {
+TRACE("***** Packing with LINK type");
+ packed_flags = (_object->_header_type << 6) | (_object->_context_flag << 5) | (_object->_transport_type << 4) | (Type::Destination::LINK << 2) | _object->_packet_type;
+ }
+ else {
+ if (!_object->_destination) {
+ throw std::logic_error("Packet destination is required");
+ }
+TRACEF("***** Packing with %d type", _object->_destination.type());
+ packed_flags = (_object->_header_type << 6) | (_object->_context_flag << 5) | (_object->_transport_type << 4) | (_object->_destination.type() << 2) | _object->_packet_type;
+ }
+ return packed_flags;
+}
+
+void Packet::unpack_flags(uint8_t flags) {
+ assert(_object);
+ _object->_header_type = static_cast<header_types>((flags & 0b01000000) >> 6);
+ _object->_context_flag = static_cast<Type::Packet::context_flags>((flags & 0b00100000) >> 5);
+ _object->_transport_type = static_cast<Type::Transport::types>((flags & 0b00010000) >> 4);
+ _object->_destination_type = static_cast<Type::Destination::types>((flags & 0b00001100) >> 2);
+ _object->_packet_type = static_cast<types>(flags & 0b00000011);
+}
+
+
+/*
+== Reticulum Wire Format ======
+
+A Reticulum packet is composed of the following fields:
+
+[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
+
+* The HEADER field is 2 bytes long.
+ * Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
+ * Byte 2: Number of hops
+
+* Interface Access Code field if the IFAC flag was set.
+ * The length of the Interface Access Code can vary from
+ 1 to 64 bytes according to physical interface
+ capabilities and configuration.
+
+* The ADDRESSES field contains either 1 or 2 addresses.
+ * Each address is 16 bytes long.
+ * The Header Type flag in the HEADER field determines
+ whether the ADDRESSES field contains 1 or 2 addresses.
+ * Addresses are SHA-256 hashes truncated to 16 bytes.
+
+* The CONTEXT field is 1 byte.
+ * It is used by Reticulum to determine packet context.
+
+* The DATA field is between 0 and 465 bytes.
+ * It contains the packets data payload.
+
+IFAC Flag
+-----------------
+open 0 Packet for publically accessible interface
+authenticated 1 Interface authentication is included in packet
+
+
+Header Types
+-----------------
+type 1 0 Two byte header, one 16 byte address field
+type 2 1 Two byte header, two 16 byte address fields
+
+
+Propagation Types
+-----------------
+broadcast 00
+transport 01
+reserved 10
+reserved 11
+
+
+Destination Types
+-----------------
+single 00
+group 01
+plain 10
+link 11
+
+
+Packet Types
+-----------------
+data 00
+announce 01
+link request 10
+proof 11
+
+
++- Packet Example -+
+
+ HEADER FIELD DESTINATION FIELDS CONTEXT FIELD DATA FIELD
+ _______|_______ ________________|________________ ________|______ __|_
+| | | | | | | |
+01010000 00000100 [HASH1, 16 bytes] [HASH2, 16 bytes] [CONTEXT, 1 byte] [DATA]
+|| | | | |
+|| | | | +-- Hops = 4
+|| | | +------- Packet Type = DATA
+|| | +--------- Destination Type = SINGLE
+|| +----------- Propagation Type = TRANSPORT
+|+------------- Header Type = HEADER_2 (two byte header, two address fields)
++-------------- Access Codes = DISABLED
+
+
++- Packet Example -+
+
+ HEADER FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
+ _______|_______ _______|_______ ________|______ __|_
+| | | | | | | |
+00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
+|| | | | |
+|| | | | +-- Hops = 7
+|| | | +------- Packet Type = DATA
+|| | +--------- Destination Type = SINGLE
+|| +----------- Propagation Type = BROADCAST
+|+------------- Header Type = HEADER_1 (two byte header, one address field)
++-------------- Access Codes = DISABLED
+
+
++- Packet Example -+
+
+ HEADER FIELD IFAC FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
+ _______|_______ ______|______ _______|_______ ________|______ __|_
+| | | | | | | | | |
+10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
+|| | | | |
+|| | | | +-- Hops = 7
+|| | | +------- Packet Type = DATA
+|| | +--------- Destination Type = SINGLE
+|| +----------- Propagation Type = BROADCAST
+|+------------- Header Type = HEADER_1 (two byte header, one address field)
++-------------- Access Codes = ENABLED
+
+
+Size examples of different packet types
+---------------------------------------
+
+The following table lists example sizes of various
+packet types. The size listed are the complete on-
+wire size counting all fields including headers,
+but excluding any interface access codes.
+
+- Path Request : 51 bytes
+- Announce : 167 bytes
+- Link Request : 83 bytes
+- Link Proof : 115 bytes
+- Link RTT packet : 99 bytes
+- Link keepalive : 20 bytes
+*/
+
+
+// Reticulum Packet Structure
+//
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |I|H|PRT|DT |PT | hops | destination... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination | context | data ... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |I|H|PRT|DT |PT | hops | destination_1... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_1... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_1... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_1... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_1 | destination_2... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_2... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_2... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_2... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ...destination_2 | context | data ... |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+void Packet::pack() {
+ assert(_object);
+ TRACE("Packet::pack: packing packet...");
+
+ if (!_object->_destination) {
+ throw std::logic_error("Packet destination is required");
+ }
+ _object->_destination_hash = _object->_destination.hash();
+
+ _object->_header.clear();
+ _object->_encrypted = false;
+
+ _object->_header << _object->_flags;
+ _object->_header << _object->_hops;
+
+ // CBA LINK
+ if (_object->_context == LRPROOF) {
+ if (!_object->_destination_link) throw std::invalid_argument("Packet is not associated with a Link");
+ TRACE("Packet::pack: destination link id: " + _object->_destination_link.link_id().toHex() );
+ _object->_header << _object->_destination_link.link_id();
+ _object->_ciphertext = _object->_data;
+ }
+ else {
+ if (_object->_header_type == HEADER_1) {
+ TRACE("Packet::pack: destination hash: " + _object->_destination.hash().toHex() );
+ _object->_header << _object->_destination.hash();
+
+ if (_object->_packet_type == ANNOUNCE) {
+ // Announce packets are not encrypted
+ _object->_ciphertext = _object->_data;
+ }
+ else if (_object->_packet_type == LINKREQUEST) {
+ // Link request packets are not encrypted
+ _object->_ciphertext = _object->_data;
+ }
+ else if (_object->_packet_type == PROOF && _object->_context == RESOURCE_PRF) {
+ // Resource proofs are not encrypted
+ _object->_ciphertext = _object->_data;
+ }
+ // CBA LINK
+ //p elif self.packet_type == Packet.PROOF and self.destination.type == RNS.Destination.LINK:
+ else if (_object->_packet_type == PROOF && _object->_destination.type() == Type::Destination::LINK) {
+ // Packet proofs over links are not encrypted
+ _object->_ciphertext = _object->_data;
+ }
+ else if (_object->_context == RESOURCE) {
+ // A resource takes care of encryption
+ // by itself
+ _object->_ciphertext = _object->_data;
+ }
+ else if (_object->_context == KEEPALIVE) {
+ // Keepalive packets contain no actual
+ // data
+ _object->_ciphertext = _object->_data;
+ }
+ else if (_object->_context == CACHE_REQUEST) {
+ // Cache-requests are not encrypted
+ _object->_ciphertext = _object->_data;
+ }
+ else {
+ // In all other cases, we encrypt the packet
+ // with the destination's encryption method
+ // CBA LINK
+ if (_object->_destination_link) {
+ _object->_ciphertext = _object->_destination_link.encrypt(_object->_data);
+TRACEF("***** Link data: %s", _object->_ciphertext.toHex().c_str());
+ }
+ else {
+ _object->_ciphertext = _object->_destination.encrypt(_object->_data);
+TRACEF("***** Destination Data: %s", _object->_ciphertext.toHex().c_str());
+ }
+ // CBA RATCHET
+ /*p TODO
+ if hasattr(self.destination, "latest_ratchet_id"):
+ self.ratchet_id = self.destination.latest_ratchet_id
+ */
+ _object->_encrypted = true;
+ }
+ }
+ else if (_object->_header_type == HEADER_2) {
+ if (!_object->_transport_id) {
+ throw std::invalid_argument("Packet with header type 2 must have a transport ID");
+ }
+ TRACE("Packet::pack: transport id: " + _object->_transport_id.toHex() );
+ TRACE("Packet::pack: destination hash: " + _object->_destination.hash().toHex() );
+ _object->_header << _object->_transport_id;
+ _object->_header << _object->_destination.hash();
+
+ if (_object->_packet_type == ANNOUNCE) {
+ // Announce packets are not encrypted
+ _object->_ciphertext = _object->_data;
+ }
+ // CBA No default encryption here like with header type HEADER_1 ???
+ // CBA Is there any packet type besides ANNOUNCE with header type HEADER_2 ???
+ // CBA Safe to assume that all HEADER_2 type packets are in transport and therefore can not and will not be decrypted locally ???
+ }
+ }
+
+ _object->_header << (uint8_t)_object->_context;
+ _object->_raw = _object->_header + _object->_ciphertext;
+
+ if (_object->_raw.size() > _object->_MTU) {
+ throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " exceeds MTU of " + std::to_string(_object->_MTU) +" bytes");
+ }
+
+ _object->_packed = true;
+ update_hash();
+}
+
+bool Packet::unpack() {
+ assert(_object);
+ TRACE("Packet::unpack: unpacking packet...");
+ try {
+ if (_object->_raw.size() < Type::Reticulum::HEADER_MINSIZE) {
+ throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " does not meet minimum header size of " + std::to_string(Type::Reticulum::HEADER_MINSIZE) +" bytes");
+ }
+
+ const uint8_t* raw = _object->_raw.data();
+
+ // read header
+ _object->_flags = raw[0];
+ _object->_hops = raw[1];
+
+ unpack_flags(_object->_flags);
+
+ // CBA TODO detect invalid flags and throw error
+ if (false) {
+ log("Received malformed packet, dropping it.");
+ return false;
+ }
+
+ if (_object->_header_type == HEADER_2) {
+ if (_object->_raw.size() < Type::Reticulum::HEADER_MAXSIZE) {
+ throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " does not meet minimum header size of " + std::to_string(Type::Reticulum::HEADER_MAXSIZE) +" bytes");
+ }
+ _object->_transport_id.assign(raw+2, Type::Reticulum::DESTINATION_LENGTH);
+ _object->_destination_hash.assign(raw+Type::Reticulum::DESTINATION_LENGTH+2, Type::Reticulum::DESTINATION_LENGTH);
+ _object->_context = static_cast<context_types>(raw[2*Type::Reticulum::DESTINATION_LENGTH+2]);
+ _object->_data.assign(raw+2*Type::Reticulum::DESTINATION_LENGTH+3, _object->_raw.size()-(2*Type::Reticulum::DESTINATION_LENGTH+3));
+ // uknown at this point whether data is encrypted or not
+ _object->_encrypted = false;
+ }
+ else {
+ _object->_transport_id.clear();
+ _object->_destination_hash.assign(raw+2, Type::Reticulum::DESTINATION_LENGTH);
+ _object->_context = static_cast<context_types>(raw[Type::Reticulum::DESTINATION_LENGTH+2]);
+ _object->_data.assign(raw+Type::Reticulum::DESTINATION_LENGTH+3, _object->_raw.size()-(Type::Reticulum::DESTINATION_LENGTH+3));
+ // uknown at this point whether data is encrypted or not
+ _object->_encrypted = false;
+ }
+
+ _object->_packed = false;
+ update_hash();
+ }
+ catch (std::exception& e) {
+ ERROR(std::string("Received malformed packet, dropping it. The contained exception was: ") + e.what());
+ return false;
+ }
+
+ return true;
+}
+
+/*
+Sends the packet.
+
+:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
+*/
+PacketReceipt Packet::send() {
+ assert(_object);
+ TRACE("Packet::send: sending packet...");
+ if (_object->_sent) {
+ throw std::logic_error("Packet was already sent");
+ }
+ // CBA LINK
+ //p if self.destination.type == RNS.Destination.LINK:
+ if (_object->_destination.type() == Type::Destination::LINK) {
+ if (!_object->_destination_link) throw std::invalid_argument("Packet is not associated with a Link");
+ if (_object->_destination_link.status() == Type::Link::CLOSED) {
+ throw std::runtime_error("Attempt to transmit over a closed link");
+ }
+ else {
+ _object->_destination_link.last_outbound(OS::time());
+ _object->_destination_link.increment_tx();
+ _object->_destination_link.increment_txbytes(_object->_data.size());
+ }
+ }
+
+ if (!_object->_packed) {
+ pack();
+ }
+
+ if (Transport::outbound(*this)) {
+ TRACE("Packet::send: successfully sent packet!!!");
+ //p return self.receipt
+ return _object->_receipt;
+ }
+ else {
+ ERROR("No interfaces could process the outbound packet");
+ _object->_sent = false;
+ _object->_receipt = {Type::NONE};
+ //p return False
+ return {Type::NONE};
+ }
+}
+
+/*
+Re-sends the packet.
+
+:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
+*/
+bool Packet::resend() {
+ assert(_object);
+ TRACE("Packet::resend: re-sending packet...");
+ if (!_object->_sent) {
+ throw std::logic_error("Packet was not sent yet");
+ }
+ // Re-pack the packet to obtain new ciphertext for
+ // encrypted destinations
+ pack();
+
+ if (Transport::outbound(*this)) {
+ TRACE("Packet::resend: successfully sent packet!!!");
+ //z return self.receipt
+ // MOCK
+ return true;
+ }
+ else {
+ ERROR("No interfaces could process the outbound packet");
+ _object->_sent = false;
+ //z self.receipt = None;
+ return false;
+ }
+}
+
+void Packet::prove(const Destination& destination /*= {Type::NONE}*/) {
+ assert(_object);
+ TRACE("Packet::prove: proving packet...");
+ // CBA LINK
+ // CBA TODO: Determine under which circumstances to use _destination and which to use _link since it's unclear from this logic
+ //p if self.fromPacked and hasattr(self, "destination") and self.destination:
+ if (_object->_fromPacked && _object->_destination) {
+ if (_object->_destination.identity() && _object->_destination.identity().prv()) {
+ _object->_destination.identity().prove(*this, destination);
+ }
+ }
+ //p elif self.fromPacked and hasattr(self, "link") and self.link:
+ else if (_object->_fromPacked && _object->_link) {
+ _object->_link.prove_packet(*this);
+ }
+ else {
+ ERROR("Could not prove packet associated with neither a destination nor a link");
+ }
+}
+
+
+// Generates a special destination that allows Reticulum
+// to direct the proof back to the proved packet's sender
+ProofDestination Packet::generate_proof_destination() const {
+ return ProofDestination(*this);
+}
+
+bool Packet::validate_proof_packet(const Packet& proof_packet) {
+ assert(_object);
+ if (!_object->_receipt) {
+ return false;
+ }
+ return _object->_receipt.validate_proof_packet(proof_packet);
+}
+
+bool Packet::validate_proof(const Bytes& proof) {
+ assert(_object);
+ if (!_object->_receipt) {
+ return false;
+ }
+ return _object->_receipt.validate_proof(proof);
+}
+
+void Packet::update_hash() {
+ assert(_object);
+ _object->_packet_hash = get_hash();
+}
+
+const Bytes Packet::get_hash() const {
+ assert(_object);
+ Bytes hashable_part = get_hashable_part();
+ // CBA MCU SHORTER HASH
+ return Identity::full_hash(hashable_part);
+ //return Identity::truncated_hash(hashable_part);
+}
+
+const Bytes Packet::getTruncatedHash() const {
+ assert(_object);
+ Bytes hashable_part = get_hashable_part();
+ return Identity::truncated_hash(hashable_part);
+}
+
+const Bytes Packet::get_hashable_part() const {
+ assert(_object);
+ Bytes hashable_part;
+ hashable_part << (uint8_t)(_object->_raw.data()[0] & 0b00001111);
+ if (_object->_header_type == HEADER_2) {
+ //p hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
+ hashable_part << _object->_raw.mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2);
+ }
+ else {
+ //p hashable_part += self.raw[2:];
+ hashable_part << _object->_raw.mid(2);
+ }
+ return hashable_part;
+}
+
+
+#ifndef NDEBUG
+std::string Packet::debugString() const {
+ if (!_object) {
+ return "";
+ }
+ if (_object->_packed) {
+ //unpack();
+ }
+ std::string str = "ph=" + _object->_packet_hash.toHex();
+ str += " ht=" + std::to_string(_object->_header_type);
+ str += " tt=" + std::to_string(_object->_transport_type);
+ str += " dt=" + std::to_string(_object->_destination_type);
+ str += " pt=" + std::to_string(_object->_packet_type);
+ str += " hp=" + std::to_string(_object->_hops);
+ str += " ti=" + _object->_transport_id.toHex();
+ str += " dh=" + _object->_destination_hash.toHex();
+ return str;
+}
+std::string Packet::dumpString() const {
+ if (!_object) {
+ return "";
+ }
+ //if (_object->_packed) {
+ // unpack();
+ //}
+ bool encrypted = true;
+ std::string dump;
+ dump = "\n------------------------------------------------------------------------------\n";
+ dump += "hash: " + _object->_packet_hash.toHex() + "\n";
+ dump += "flags: " + hexFromByte(_object->_flags) + "\n";
+ //dump += " header_type: " + std::to_string(_object->_header_type) + "\n";
+ dump += " header_type: ";
+ switch (_object->_header_type) {
+ case HEADER_1:
+ dump += "HEADER_1\n";
+ break;
+ case HEADER_2:
+ encrypted = false;
+ dump += "HEADER_2\n";
+ break;
+ default:
+ std::to_string(_object->_header_type) + "\n";
+ }
+ //dump += " transport_type: " + std::to_string(_object->_transport_type) + "\n";
+ dump += " transport_type: ";
+ switch (_object->_transport_type) {
+ case Type::Transport::BROADCAST:
+ dump += "BROADCAST\n";
+ break;
+ case Type::Transport::TRANSPORT:
+ dump += "TRANSPORT\n";
+ break;
+ case Type::Transport::RELAY:
+ dump += "RELAY\n";
+ break;
+ case Type::Transport::TUNNEL:
+ dump += "TUNNEL\n";
+ break;
+ case Type::Transport::NONE:
+ dump += "NONE\n";
+ break;
+ default:
+ std::to_string(_object->_transport_type) + "\n";
+ }
+ //dump += " destination_type: " + std::to_string(_object->_destination_type) + "\n";
+ dump += " destination_type: ";
+ switch (_object->_destination_type) {
+ case Type::Destination::SINGLE:
+ dump += "SINGLE\n";
+ break;
+ case Type::Destination::GROUP:
+ dump += "GROUP\n";
+ break;
+ case Type::Destination::PLAIN:
+ dump += "PLAIN\n";
+ break;
+ case Type::Destination::LINK:
+ dump += "LINK\n";
+ break;
+ default:
+ std::to_string(_object->_destination_type) + "\n";
+ }
+ //dump += " packet_type: " + std::to_string(_object->_packet_type) + "\n";
+ dump += " packet_type: ";
+ switch (_object->_packet_type) {
+ case DATA:
+ dump += "DATA\n";
+ break;
+ case ANNOUNCE:
+ encrypted = false;
+ dump += "ANNOUNCE\n";
+ break;
+ case LINKREQUEST:
+ encrypted = false;
+ dump += "LINKREQUEST\n";
+ break;
+ case PROOF:
+ dump += "PROOF\n";
+ if (_object->_context == RESOURCE_PRF) {
+ encrypted = false;
+ }
+ // CBA LINK
+ if (_object->_destination && _object->_destination.type() == Type::Destination::LINK) {
+ encrypted = false;
+ }
+ break;
+ default:
+ std::to_string(_object->_packet_type) + "\n";
+ }
+ dump += "hops: " + std::to_string(_object->_hops) + "\n";
+ dump += "transport: " + _object->_transport_id.toHex() + "\n";
+ dump += "destination: " + _object->_destination_hash.toHex() + "\n";
+ //dump += "context: " + std::to_string(_object->_context) + "\n";
+ dump += "context: ";
+ switch (_object->_context) {
+ case CONTEXT_NONE:
+ dump += "CONTEXT_NONE\n";
+ break;
+ case RESOURCE:
+ encrypted = false;
+ dump += "RESOURCE\n";
+ break;
+ case RESOURCE_ADV:
+ dump += "RESOURCE_ADV\n";
+ break;
+ case RESOURCE_REQ:
+ dump += "RESOURCE_REQ\n";
+ break;
+ case RESOURCE_HMU:
+ dump += "RESOURCE_HMU\n";
+ break;
+ case RESOURCE_PRF:
+ encrypted = false;
+ dump += "RESOURCE_PRF\n";
+ break;
+ case RESOURCE_ICL:
+ dump += "RESOURCE_ICL\n";
+ break;
+ case RESOURCE_RCL:
+ dump += "RESOURCE_RCL\n";
+ break;
+ case CACHE_REQUEST:
+ encrypted = false;
+ dump += "CACHE_REQUEST\n";
+ break;
+ case REQUEST:
+ dump += "REQUEST\n";
+ break;
+ case RESPONSE:
+ dump += "RESPONSE\n";
+ break;
+ case PATH_RESPONSE:
+ dump += "PATH_RESPONSE\n";
+ break;
+ case COMMAND:
+ dump += "COMMAND\n";
+ break;
+ case COMMAND_STATUS:
+ dump += "COMMAND_STATUS\n";
+ break;
+ case CHANNEL:
+ dump += "CHANNEL\n";
+ break;
+ case KEEPALIVE:
+ encrypted = false;
+ dump += "KEEPALIVE\n";
+ break;
+ case LINKIDENTIFY:
+ dump += "LINKIDENTIFY\n";
+ break;
+ case LINKCLOSE:
+ dump += "LINKCLOSE\n";
+ break;
+ case LINKPROOF:
+ dump += "LINKPROOF\n";
+ break;
+ case LRRTT:
+ dump += "LRRTT\n";
+ break;
+ case LRPROOF:
+ encrypted = false;
+ dump += "LRPROOF\n";
+ break;
+ default:
+ std::to_string(_object->_context) + "\n";
+ }
+ dump += "raw: " + _object->_raw.toHex() + "\n";
+ dump += " length: " + std::to_string(_object->_raw.size()) + "\n";
+ dump += "data: " + _object->_data.toHex() + "\n";
+ dump += " length: " + std::to_string(_object->_data.size()) + "\n";
+ //if ((encrypted || _object->_encrypted) && _object->_raw.size() > 0) {
+ if (false) {
+ size_t header_len = Type::Reticulum::HEADER_MINSIZE;
+ if (_object->_header_type == HEADER_2) {
+ header_len = Type::Reticulum::HEADER_MAXSIZE;
+ }
+ dump += "encrypted:\n";
+ dump += " header: " + _object->_raw.left(header_len).toHex() + "\n";
+ dump += " key: " + _object->_raw.mid(header_len, Type::Identity::KEYSIZE/8/2).toHex() + "\n";
+ Bytes ciphertext(_object->_raw.mid(header_len+Type::Identity::KEYSIZE/8/2));
+ dump += " ciphertext: " + ciphertext.toHex() + "\n";
+ dump += " length: " + std::to_string(ciphertext.size()) + "\n";
+ dump += " iv: " + ciphertext.left(16).toHex() + "\n";
+ dump += " sig: " + ciphertext.right(32).toHex() + "\n";
+ if (ciphertext.size() >= 48) {
+ dump += " aes ciphertext: " + ciphertext.mid(16, ciphertext.size()-48).toHex() + "\n";
+ dump += " length: " + std::to_string(ciphertext.size()-48) + "\n";
+ }
+ }
+ dump += "------------------------------------------------------------------------------\n";
+ return dump;
+}
+#endif
+
+
+PacketReceipt::PacketReceipt(const Packet& packet) : _object(new Object()) {
+
+ if (!packet.destination()) {
+ throw std::invalid_argument("Packet with destination is required");
+ }
+ _object->_hash = packet.get_hash();
+ _object->_truncated_hash = packet.getTruncatedHash();
+ _object->_destination = packet.destination();
+
+ // CBA LINK
+ if (packet.destination().type() == Type::Destination::LINK) {
+ if (!packet.destination_link()) throw std::invalid_argument("Packet is not associated with a Link");
+ //p self.timeout = max(packet.destination.rtt * packet.destination.traffic_timeout_factor, RNS.Link.TRAFFIC_TIMEOUT_MIN_MS/1000)
+ _object->_timeout = std::max(packet.destination_link().rtt() * packet.destination_link().traffic_timeout_factor(), (double)RNS::Type::Link::TRAFFIC_TIMEOUT_MIN_MS/1000);
+ }
+ else {
+ //p self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(self.destination.hash)
+ //p self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
+ _object->_timeout = RNS::Reticulum::get_instance().get_first_hop_timeout(_object->_destination.hash());
+ _object->_timeout += TIMEOUT_PER_HOP * Transport::hops_to(_object->_destination.hash());
+ }
+}
+
+// Validate a proof packet
+bool PacketReceipt::validate_proof_packet(const Packet& proof_packet) {
+ if (proof_packet.link()) {
+ return validate_link_proof(proof_packet.data(), proof_packet.link(), proof_packet);
+ }
+ else {
+ return validate_proof(proof_packet.data(), proof_packet);
+ }
+}
+
+// Validate a raw proof for a link
+//bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet /*= {Type::NONE}*/) {
+bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link) {
+ return validate_link_proof(proof, link, {Type::NONE});
+}
+bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet) {
+ assert(_object);
+ TRACE("PacketReceipt::validate_link_proof: validating link proof...");
+ // TODO: Hardcoded as explicit proofs for now
+ if (true || proof.size() == EXPL_LENGTH) {
+ // This is an explicit proof
+ Bytes proof_hash = proof.left(Type::Identity::HASHLENGTH/8);
+ Bytes signature = proof.mid(Type::Identity::HASHLENGTH/8, Type::Identity::SIGLENGTH/8);
+ if (proof_hash == _object->_hash) {
+ //z if (link.validate(signature, _object->_hash)) {
+ if (false) {
+ _object->_status = DELIVERED;
+ _object->_proved = true;
+ _object->_concluded_at = OS::time();
+ //z _object->_proof_packet = proof_packet;
+ //z link.last_proof(_object->_concluded_at);
+
+ if (_object->_callbacks._delivery) {
+ try {
+ _object->_callbacks._delivery(*this);
+ }
+ catch (std::exception& e) {
+ ERROR("An error occurred while evaluating external delivery callback for " + link.toString());
+ ERROR("The contained exception was: " + std::string(e.what()));
+ }
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+ }
+ else if (proof.size() == IMPL_LENGTH) {
+ // TODO: Why is this disabled?
+ // signature = proof[:RNS.Identity.SIGLENGTH//8]
+ // proof_valid = self.link.validate(signature, self.hash)
+ // if proof_valid:
+ // self.status = PacketReceipt.DELIVERED
+ // self.proved = True
+ // self.concluded_at = time.time()
+ // if self.callbacks.delivery != None:
+ // self.callbacks.delivery(self)
+ // RNS.log("valid")
+ // return True
+ // else:
+ // RNS.log("invalid")
+ // return False
+ }
+ else {
+ return false;
+ }
+ return false;
+}
+
+// Validate a raw proof
+//bool PacketReceipt::validate_proof(const Bytes& proof, const Packet& proof_packet /*= {Type::NONE}*/) {
+bool PacketReceipt::validate_proof(const Bytes& proof) {
+ return validate_proof(proof, {Type::NONE});
+}
+bool PacketReceipt::validate_proof(const Bytes& proof, const Packet& proof_packet) {
+ assert(_object);
+ TRACE("PacketReceipt::validate_proof: validating proof...");
+ // CBA LINK
+ // CBA TODO: Determine whether to use destination.identity or link.identity here!!!
+ if (proof.size() == EXPL_LENGTH) {
+ // This is an explicit proof
+ Bytes proof_hash = proof.left(Type::Identity::HASHLENGTH/8);
+ Bytes signature = proof.mid(Type::Identity::HASHLENGTH/8, Type::Identity::SIGLENGTH/8);
+ if (proof_hash == _object->_hash) {
+ if (_object->_destination.identity().validate(signature, _object->_hash)) {
+ _object->_status = DELIVERED;
+ _object->_proved = true;
+ _object->_concluded_at = OS::time();
+ //z _object->_proof_packet = proof_packet;
+
+ if (_object->_callbacks._delivery) {
+ try {
+ _object->_callbacks._delivery(*this);
+ }
+ catch (std::exception& e) {
+ ERROR("Error while executing proof validated callback. The contained exception was: " + std::string(e.what()));
+ }
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+ }
+ else if (proof.size() == IMPL_LENGTH) {
+ // This is an implicit proof
+ if (!_object->_destination.identity()) {
+ return false;
+ }
+
+ Bytes signature = proof.left(Type::Identity::SIGLENGTH/8);
+ if (_object->_destination.identity().validate(signature, _object->_hash)) {
+ _object->_status = DELIVERED;
+ _object->_proved = true;
+ _object->_concluded_at = OS::time();
+ //z _object->_proof_packet = proof_packet;
+
+ if (_object->_callbacks._delivery) {
+ try {
+ _object->_callbacks._delivery(*this);
+ }
+ catch (std::exception& e) {
+ ERROR("Error while executing proof validated callback. The contained exception was: " + std::string(e.what()));
+ }
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+ return false;
+}
+
+void PacketReceipt::check_timeout() {
+ assert(_object);
+ if (_object->_status == SENT && is_timed_out()) {
+ if (_object->_timeout == -1) {
+ _object->_status = CULLED;
+ }
+ else {
+ _object->_status = FAILED;
+ }
+
+ _object->_concluded_at = Utilities::OS::time();
+
+ if (_object->_callbacks._timeout) {
+ //z thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
+ //z thread.daemon = True
+ //z thread.start();
+ }
+ }
+}
+
+/*
+void ArduinoJson::convertFromJson(JsonVariantConst src, RNS::Packet& dst) {
+ if (!src.isNull()) {
+ RNS::Bytes hash;
+ hash.assignHex(src.as<const char*>());
+ // Query transport for matching interface
+ dst = Transport::get_cached_packet(hash);
+ }
+ else {
+ dst = {RNS::Type::NONE};
+ }
+}
+*/
\ No newline at end of file

diff --git a/lib/microReticulum/src/Packet.h b/lib/microReticulum/src/Packet.h
new file mode 100755
index 0000000..c6898ef
--- /dev/null
+++ b/lib/microReticulum/src/Packet.h
@@ -0,0 +1,382 @@
+#pragma once
+
+#include "Link.h"
+#include "Interface.h"
+#include "Destination.h"
+#include "Bytes.h"
+#include "Log.h"
+#include "Type.h"
+#include "Utilities/OS.h"
+
+#include <memory>
+#include <cassert>
+#include <stdint.h>
+#include <time.h>
+
+namespace RNS {
+
+ class ProofDestination;
+ class PacketReceipt;
+ class Packet;
+
+ class ProofDestination : public Destination {
+ public:
+ ProofDestination(const Packet& packet);
+ // CBA Can't use virtual methods because they are lost in object copies
+ //inline virtual const Bytes encrypt(const Bytes& data) {
+ // return data;
+ //}
+ };
+
+ /*
+ The PacketReceipt class is used to receive notifications about
+ :ref:`RNS.Packet<api-packet>` instances sent over the network. Instances
+ of this class are never created manually, but always returned from
+ the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
+ */
+ class PacketReceipt {
+
+ public:
+ class Callbacks {
+ public:
+ using delivery = void(*)(const PacketReceipt& packet_receipt);
+ using timeout = void(*)(const PacketReceipt& packet_receipt);
+ public:
+ delivery _delivery = nullptr;
+ timeout _timeout = nullptr;
+ friend class PacketReceipt;
+ };
+
+ public:
+ PacketReceipt() : _object(new Object()) {}
+ PacketReceipt(Type::NoneConstructor none) {}
+ PacketReceipt(const PacketReceipt& packet_receipt) : _object(packet_receipt._object) {}
+ PacketReceipt(const Packet& packet);
+
+ inline PacketReceipt& operator = (const PacketReceipt& packet_receipt) {
+ _object = packet_receipt._object;
+ return *this;
+ }
+ inline operator bool() const {
+ return _object.get() != nullptr;
+ }
+ inline bool operator < (const PacketReceipt& packet_receipt) const {
+ return _object.get() < packet_receipt._object.get();
+ }
+
+ public:
+ bool validate_proof_packet(const Packet& proof_packet);
+ //bool validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet = {Type::NONE});
+ bool validate_link_proof(const Bytes& proof, const Link& link);
+ bool validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet);
+ //bool validate_proof(const Bytes& proof, const Packet& proof_packet = {Type::NONE});
+ bool validate_proof(const Bytes& proof);
+ bool validate_proof(const Bytes& proof, const Packet& proof_packet);
+ inline double get_rtt() { assert(_object); return _object->_concluded_at - _object->_sent_at; }
+ inline bool is_timed_out() { assert(_object); return ((_object->_sent_at + _object->_timeout) < Utilities::OS::time()); }
+ void check_timeout();
+
+ // :param timeout: The timeout in seconds.
+ inline void set_timeout(int16_t timeout) { assert(_object); _object->_timeout = timeout; }
+
+ /*
+ Sets a function that gets called if a successfull delivery has been proven.
+
+ :param callback: A *callable* with the signature *callback(packet_receipt)*
+ */
+ inline void set_delivery_callback(Callbacks::delivery callback) {
+ assert(_object);
+ _object->_callbacks._delivery = callback;
+ }
+
+ /*
+ Sets a function that gets called if the delivery times out.
+
+ :param callback: A *callable* with the signature *callback(packet_receipt)*
+ */
+ inline void set_timeout_callback(Callbacks::timeout callback) {
+ assert(_object);
+ _object->_callbacks._timeout = callback;
+ }
+
+ // getters
+ inline const Bytes& hash() const { assert(_object); return _object->_hash; }
+ inline Type::PacketReceipt::Status status() const { assert(_object); return _object->_status; }
+ inline bool proved() const { assert(_object); return _object->_proved; }
+ inline double concluded_at() const { assert(_object); return _object->_concluded_at; }
+ inline const Bytes& truncated_hash() const { assert(_object); return _object->_truncated_hash; }
+ inline const Callbacks& callbacks() const { assert(_object); return _object->_callbacks; }
+
+ // setters
+ inline void status(Type::PacketReceipt::Status status) { assert(_object); _object->_status = status; }
+ inline void proved(bool proved) { assert(_object); _object->_proved = proved; }
+ inline void concluded_at(double concluded_at) { assert(_object); _object->_concluded_at = concluded_at; }
+
+ private:
+ class Object {
+ public:
+ Object() {}
+ virtual ~Object() {}
+ private:
+ Bytes _hash;
+ Bytes _truncated_hash;
+ bool _sent = true;
+ double _sent_at = Utilities::OS::time();
+ bool _proved = false;
+ Type::PacketReceipt::Status _status = Type::PacketReceipt::SENT;
+ Destination _destination = {Type::NONE};
+ Callbacks _callbacks;
+ double _concluded_at = 0;
+ // CBA TODO This shoujld almost certainly not be a reference but we have an issue with circular dependency between Packet and PacketReceipt
+ //Packet _proof_packet = {Type::NONE};
+ int16_t _timeout = 0;
+ friend class PacketReceipt;
+ };
+ std::shared_ptr<Object> _object;
+
+ };
+
+
+ class Packet {
+
+ public:
+ //static constexpr const uint8_t EMPTY_DESTINATION[Type::Reticulum::DESTINATION_LENGTH] = {0};
+ uint8_t EMPTY_DESTINATION[Type::Reticulum::DESTINATION_LENGTH] = {0};
+
+ public:
+ Packet(Type::NoneConstructor none) {
+ MEM("Packet NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+ Packet(const Packet& packet) : _object(packet._object) {
+ MEM("Packet object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+ Packet(
+ const Destination& destination,
+ const Interface& attached_interface,
+ const Bytes& data,
+ Type::Packet::types packet_type = Type::Packet::DATA,
+ Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
+ Type::Transport::types transport_type = Type::Transport::BROADCAST,
+ Type::Packet::header_types header_type = Type::Packet::HEADER_1,
+ const Bytes& transport_id = {Bytes::NONE},
+ bool create_receipt = true,
+ Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
+ );
+ Packet(
+ const Destination& destination,
+ const Bytes& data,
+ Type::Packet::types packet_type = Type::Packet::DATA,
+ Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
+ Type::Transport::types transport_type = Type::Transport::BROADCAST,
+ Type::Packet::header_types header_type = Type::Packet::HEADER_1,
+ const Bytes& transport_id = {Bytes::NONE},
+ bool create_receipt = true,
+ Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
+ ) : Packet(destination, {Type::NONE}, data, packet_type, context, transport_type, header_type, transport_id, create_receipt, context_flag) {}
+ // CBA LINK
+ Packet(
+ const Link& link,
+ const Bytes& data,
+ Type::Packet::types packet_type = Type::Packet::DATA,
+ Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
+ Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
+ );
+ virtual ~Packet() {
+ MEM("Packet object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+
+ inline Packet& operator = (const Packet& packet) {
+ _object = packet._object;
+ MEM("Packet object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ return *this;
+ }
+ inline operator bool() const {
+ return _object.get() != nullptr;
+ }
+ inline bool operator < (const Packet& packet) const {
+ return _object.get() < packet._object.get();
+ }
+
+ private:
+ /*
+ void setTransportId(const uint8_t* transport_id);
+ void setHeader(const uint8_t* header);
+ void setRaw(const uint8_t* raw, uint16_t len);
+ void setData(const uint8_t* rata, uint16_t len);
+ */
+
+ public:
+ uint8_t get_packed_flags();
+ void unpack_flags(uint8_t flags);
+ void pack();
+ bool unpack();
+ PacketReceipt send();
+ bool resend();
+ void prove(const Destination& destination = {Type::NONE});
+ ProofDestination generate_proof_destination() const;
+ bool validate_proof_packet(const Packet& proof_packet);
+ bool validate_proof(const Bytes& proof);
+ void update_hash();
+ const Bytes get_hash() const;
+ const Bytes getTruncatedHash() const;
+ const Bytes get_hashable_part() const;
+
+ inline std::string toString() const { if (!_object) return ""; return "{Packet:" + _object->_packet_hash.toHex() + "}"; }
+
+ // getters
+ inline const Destination& destination() const { assert(_object); return _object->_destination; }
+ inline const Link& link() const { assert(_object); return _object->_link; }
+ inline const Interface& attached_interface() const { assert(_object); return _object->_attached_interface; }
+ inline const Interface& receiving_interface() const { assert(_object); return _object->_receiving_interface; }
+ inline Type::Packet::header_types header_type() const { assert(_object); return _object->_header_type; }
+ inline Type::Packet::context_flags context_flag() const { assert(_object); return _object->_context_flag; }
+ inline Type::Transport::types transport_type() const { assert(_object); return _object->_transport_type; }
+ inline Type::Destination::types destination_type() const { assert(_object); return _object->_destination_type; }
+ inline Type::Packet::types packet_type() const { assert(_object); return _object->_packet_type; }
+ inline Type::Packet::context_types context() const { assert(_object); return _object->_context; }
+ inline bool sent() const { assert(_object); return _object->_sent; }
+ inline double sent_at() const { assert(_object); return _object->_sent_at; }
+ inline bool create_receipt() const { assert(_object); return _object->_create_receipt; }
+ inline const PacketReceipt& receipt() const { assert(_object); return _object->_receipt; }
+ inline uint8_t flags() const { assert(_object); return _object->_flags; }
+ inline uint8_t hops() const { assert(_object); return _object->_hops; }
+ inline bool cached() const { assert(_object); return _object->_cached; }
+ inline const Bytes& packet_hash() const { assert(_object); return _object->_packet_hash; }
+ inline const Bytes& destination_hash() const { assert(_object); return _object->_destination_hash; }
+ inline const Bytes& transport_id() const { assert(_object); return _object->_transport_id; }
+ inline const Bytes& raw() const { assert(_object); return _object->_raw; }
+ inline const Bytes& data() const { assert(_object); return _object->_data; }
+ // CBA LINK
+ inline const Link& destination_link() const { assert(_object); return _object->_destination_link; }
+ //CBA Following method is only used by Resource to access decrypted resource advertisement form Link. Consider a better way.
+ inline const Bytes& plaintext() { assert(_object); return _object->_plaintext; }
+
+ // setters
+ inline void destination(const Destination& destination) { assert(_object); _object->_destination = destination; }
+ inline void link(const Link& link) { assert(_object); _object->_link = link; }
+ inline void receiving_interface(const Interface& receiving_interface) { assert(_object); _object->_receiving_interface = receiving_interface; }
+ inline void sent(bool sent) { assert(_object); _object->_sent = sent; }
+ inline void sent_at(double sent_at) { assert(_object); _object->_sent_at = sent_at; }
+ inline void receipt(const PacketReceipt& receipt) { assert(_object); _object->_receipt = receipt; }
+ inline void hops(uint8_t hops) { assert(_object); _object->_hops = hops; }
+ inline void cached(bool cached) { assert(_object); _object->_cached = cached; }
+ inline void transport_id(const Bytes& transport_id) { assert(_object); _object->_transport_id = transport_id; }
+ //CBA Following method is only used by Link to provide Resource access to decrypted resource advertisement. Consider a better way.
+ inline void plaintext(const Bytes& plaintext) { assert(_object); _object->_plaintext = plaintext; }
+
+#ifndef NDEBUG
+ std::string debugString() const;
+ std::string dumpString() const;
+#endif
+
+ private:
+ class Object {
+ public:
+ Object(const Destination& destination, const Interface& attached_interface) : _destination(destination), _attached_interface(attached_interface) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
+ // CBA LINK
+ //Object(const Destination& destination, const Link& destination_link) : _destination(destination), _destination_link(destination_link) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
+ //Object(const Link& link) : _destination(link.destination()), _destination_link(link) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
+ virtual ~Object() { MEM("Packet::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
+ private:
+ Destination _destination = {Type::NONE};
+
+ // CBA LINK
+ // CBA TODO: Determine if _link (assigned late by Transport) and _destination_link (assigned in constructor) can be one and the same !!!
+ Link _destination_link = {Type::NONE};
+
+ Link _link = {Type::NONE};
+
+ Interface _attached_interface = {Type::NONE};
+ Interface _receiving_interface = {Type::NONE};
+
+ Type::Packet::header_types _header_type = Type::Packet::HEADER_1;
+ Type::Transport::types _transport_type = Type::Transport::BROADCAST;
+ Type::Destination::types _destination_type = Type::Destination::SINGLE;
+ Type::Packet::types _packet_type = Type::Packet::DATA;
+ Type::Packet::context_types _context = Type::Packet::CONTEXT_NONE;
+ Type::Packet::context_flags _context_flag = Type::Packet::FLAG_UNSET;
+
+ uint8_t _flags = 0;
+ uint8_t _hops = 0;
+
+ bool _packed = false;
+ bool _sent = false;
+ bool _create_receipt = false;
+ bool _fromPacked = false;
+ bool _truncated = false; // whether data was truncated
+ bool _encrypted = false; // whether data is encrypted
+ bool _cached = false; // whether packet has been cached
+ PacketReceipt _receipt = {Type::NONE};
+
+ uint16_t _MTU = Type::Reticulum::MTU;
+ double _sent_at = 0;
+
+ float _rssi = 0.0;
+ float _snr = 0.0;
+ float _q = 0.0;
+
+ Bytes _packet_hash;
+ Bytes _ratchet_id;
+ Bytes _destination_hash;
+ Bytes _transport_id;
+
+ Bytes _raw; // header + ( plaintext | ciphertext-token )
+ Bytes _data; // plaintext | ciphertext
+
+ Bytes _plaintext; // used exclusively to relay decrypted resource advertisement form Link to Resource
+
+ Bytes _header;
+ Bytes _ciphertext;
+
+ friend class Packet;
+ };
+ std::shared_ptr<Object> _object;
+
+ };
+
+}
+
+/*
+namespace ArduinoJson {
+ inline bool convertToJson(const RNS::Packet& src, JsonVariant dst) {
+ if (!src) {
+ return dst.set(nullptr);
+ }
+ return dst.set(src.get_hash().toHex());
+ }
+ void convertFromJson(JsonVariantConst src, RNS::Packet& dst);
+ inline bool canConvertFromJson(JsonVariantConst src, const RNS::Packet&) {
+ return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
+ }
+}
+*/
+/*
+namespace ArduinoJson {
+ template <>
+ struct Converter<RNS::Packet> {
+ static bool toJson(const RNS::Packet& src, JsonVariant dst) {
+ if (!src) {
+ return dst.set(nullptr);
+ }
+ TRACE("<<< Serializing packet hash " + src.get_hash().toHex());
+ return dst.set(src.get_hash().toHex());
+ }
+ static RNS::Packet fromJson(JsonVariantConst src) {
+ if (!src.isNull()) {
+ RNS::Bytes hash;
+ hash.assignHex(src.as<const char*>());
+ TRACE(">>> Deserialized packet hash " + hash.toHex());
+ TRACE(">>> Querying transport for cached packet");
+ // Query transport for matching interface
+ return RNS::Packet::get_cached_packet(hash);
+ }
+ else {
+ return {RNS::Type::NONE};
+ }
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
+ }
+ };
+}
+*/

diff --git a/lib/microReticulum/src/Resource.cpp b/lib/microReticulum/src/Resource.cpp
new file mode 100755
index 0000000..cb4a653
--- /dev/null
+++ b/lib/microReticulum/src/Resource.cpp
@@ -0,0 +1,122 @@
+#include "Resource.h"
+
+#include "ResourceData.h"
+#include "Reticulum.h"
+#include "Transport.h"
+#include "Packet.h"
+#include "Log.h"
+
+#include <algorithm>
+
+using namespace RNS;
+using namespace RNS::Type::Resource;
+using namespace RNS::Utilities;
+
+//Resource::Resource(const Link& link /*= {Type::NONE}*/) :
+// _object(new ResourceData(link))
+//{
+// assert(_object);
+// MEM("Resource object created");
+//}
+
+Resource::Resource(const Bytes& data, const Link& link, const Bytes& request_id, bool is_response, double timeout) :
+ _object(new ResourceData(link))
+{
+ assert(_object);
+ MEM("Resource object created");
+}
+
+Resource::Resource(const Bytes& data, const Link& link, bool advertise /*= true*/, bool auto_compress /*= true*/, Callbacks::concluded callback /*= nullptr*/, Callbacks::progress progress_callback /*= nullptr*/, double timeout /*= 0.0*/, int segment_index /*= 1*/, const Bytes& original_hash /*= {Type::NONE}*/, const Bytes& request_id /*= {Type::NONE}*/, bool is_response /*= false*/) :
+ _object(new ResourceData(link))
+{
+ assert(_object);
+ MEM("Resource object created");
+}
+
+
+void Resource::validate_proof(const Bytes& proof_data) {
+}
+
+void Resource::cancel() {
+}
+
+/*
+:returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0.
+*/
+float Resource::get_progress() const {
+/*
+ assert(_object);
+ if (_object->_initiator) {
+ _object->_processed_parts = (_object->_segment_index-1)*math.ceil(Type::Resource::MAX_EFFICIENT_SIZE/Type::Resource::SDU);
+ _object->_processed_parts += _object->sent_parts;
+ _object->_progress_total_parts = float(_object->grand_total_parts);
+ }
+ else {
+ _object->_processed_parts = (_object->_segment_index-1)*math.ceil(Type::Resource::MAX_EFFICIENT_SIZE/Type::Resource::SDU);
+ _object->_processed_parts += _object->_received_count;
+ if (_object->split) {
+ _object->progress_total_parts = float(math.ceil(_object->total_size/Type::Resource::SDU));
+ }
+ else {
+ _object->progress_total_parts = float(_object->total_parts);
+ }
+ }
+
+ return (float)_object->processed_parts / (float)_object->progress_total_parts;
+*/
+ return 0.0;
+}
+
+void Resource::set_concluded_callback(Callbacks::concluded callback) {
+ assert(_object);
+ _object->_callbacks._concluded = callback;
+}
+
+void Resource::set_progress_callback(Callbacks::progress callback) {
+ assert(_object);
+ _object->_callbacks._progress = callback;
+}
+
+
+std::string Resource::toString() const {
+ if (!_object) {
+ return "";
+ }
+ //return "<"+RNS.hexrep(self.hash,delimit=False)+"/"+RNS.hexrep(self.link.link_id,delimit=False)+">"
+ //return "{Resource:" + _object->_hash.toHex() + "}";
+ return "{Resource: unknown}";
+}
+
+// getters
+const Bytes& Resource::hash() const {
+ assert(_object);
+ return _object->_hash;
+}
+
+const Bytes& Resource::request_id() const {
+ assert(_object);
+ return _object->_request_id;
+}
+
+const Bytes& Resource::data() const {
+ assert(_object);
+ return _object->_data;
+}
+
+const Type::Resource::status Resource::status() const {
+ assert(_object);
+ return _object->_status;
+}
+
+const size_t Resource::size() const {
+ assert(_object);
+ return _object->_size;
+}
+
+const size_t Resource::total_size() const {
+ assert(_object);
+ return _object->_total_size;
+}
+
+// setters
+

diff --git a/lib/microReticulum/src/Resource.h b/lib/microReticulum/src/Resource.h
new file mode 100755
index 0000000..e1cd909
--- /dev/null
+++ b/lib/microReticulum/src/Resource.h
@@ -0,0 +1,110 @@
+#pragma once
+
+#include "Destination.h"
+#include "Type.h"
+
+#include <memory>
+#include <cassert>
+
+namespace RNS {
+
+ class ResourceData;
+ class Packet;
+ class Destination;
+ class Link;
+ class Resource;
+
+ class Resource {
+
+ public:
+ class Callbacks {
+ public:
+ // CBA std::function apparently not implemented in NRF52 framework
+ //typedef std::function<void(const Resource& resource)> concluded;
+ using concluded = void(*)(const Resource& resource);
+ using progress = void(*)(const Resource& resource);
+ public:
+ concluded _concluded = nullptr;
+ progress _progress = nullptr;
+ friend class Resource;
+ };
+
+ public:
+ Resource(Type::NoneConstructor none) {
+ MEM("Resource NONE object created");
+ }
+ Resource(const Resource& resource) : _object(resource._object) {
+ MEM("Resource object copy created");
+ }
+ //Resource(const Link& link = {Type::NONE});
+ Resource(const Bytes& data, const Link& link, const Bytes& request_id, bool is_response, double timeout);
+ Resource(const Bytes& data, const Link& link, bool advertise = true, bool auto_compress = true, Callbacks::concluded callback = nullptr, Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int segment_index = 1, const Bytes& original_hash = {Type::NONE}, const Bytes& request_id = {Type::NONE}, bool is_response = false);
+ virtual ~Resource(){
+ MEM("Resource object destroyed");
+ }
+
+ Resource& operator = (const Resource& resource) {
+ _object = resource._object;
+ return *this;
+ }
+ operator bool() const {
+ return _object.get() != nullptr;
+ }
+ bool operator < (const Resource& resource) const {
+ return _object.get() < resource._object.get();
+ //return _object->_hash < resource._object->_hash;
+ }
+
+ public:
+ //p static def accept(advertisement_packet, callback=None, progress_callback = None, request_id = None):
+
+ public:
+//p def hashmap_update_packet(self, plaintext):
+//p def hashmap_update(self, segment, hashmap):
+//p def get_map_hash(self, data):
+//p def advertise(self):
+//p def __advertise_job(self):
+//p def watchdog_job(self):
+//p def __watchdog_job(self):
+//p def assemble(self):
+//p def prove(self):
+ void validate_proof(const Bytes& proof_data);
+//p def receive_part(self, packet):
+//p def request_next(self):
+//p def request(self, request_data):
+ void cancel();
+//p def set_callback(self, callback):
+//p def progress_callback(self, callback):
+ float get_progress() const;
+//p def get_transfer_size(self):
+//p def get_data_size(self):
+//p def get_parts(self):
+//p def get_segments(self):
+//p def get_hash(self):
+//p def is_compressed(self):
+ void set_concluded_callback(Callbacks::concluded callback);
+ void set_progress_callback(Callbacks::progress callback);
+
+ std::string toString() const;
+
+ // getters
+ const Bytes& hash() const;
+ const Bytes& request_id() const;
+ const Bytes& data() const;
+ const Type::Resource::status status() const;
+ const size_t size() const;
+ const size_t total_size() const;
+
+ // setters
+
+ protected:
+ std::shared_ptr<ResourceData> _object;
+
+ };
+
+
+ class ResourceAdvertisement {
+
+ };
+
+}

diff --git a/lib/microReticulum/src/ResourceData.h b/lib/microReticulum/src/ResourceData.h
new file mode 100755
index 0000000..86cd18f
--- /dev/null
+++ b/lib/microReticulum/src/ResourceData.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "Resource.h"
+
+#include "Interface.h"
+#include "Packet.h"
+#include "Destination.h"
+#include "Bytes.h"
+#include "Type.h"
+#include "Cryptography/Fernet.h"
+
+namespace RNS {
+
+ class ResourceData {
+ public:
+ ResourceData(const Link& link) : _link(link) {}
+ virtual ~ResourceData() {}
+ private:
+ Link _link;
+ Bytes _hash;
+ Bytes _request_id;
+ Bytes _data;
+ Type::Resource::status _status = Type::Resource::NONE;
+ size_t _size = 0;
+ size_t _total_size = 0;
+ Resource::Callbacks _callbacks;
+
+ friend class Resource;
+ };
+
+}

diff --git a/lib/microReticulum/src/Reticulum.cpp b/lib/microReticulum/src/Reticulum.cpp
new file mode 100755
index 0000000..0e49c1b
--- /dev/null
+++ b/lib/microReticulum/src/Reticulum.cpp
@@ -0,0 +1,492 @@
+#include "Reticulum.h"
+
+#include "Transport.h"
+#include "Log.h"
+
+//#include <TransistorNoiseSource.h>
+#include <RNG.h>
+
+#ifdef ARDUINO
+#include <Arduino.h>
+//#include <TransistorNoiseSource.h>
+#endif
+
+using namespace RNS;
+using namespace RNS::Type::Reticulum;
+using namespace RNS::Utilities;
+
+/*static*/ //std::string Reticulum::_storagepath;
+/*static*/ char Reticulum::_storagepath[FILEPATH_MAXSIZE];
+/*static*/ //std::string Reticulum::_cachepath;
+/*static*/ char Reticulum::_cachepath[FILEPATH_MAXSIZE];
+
+/*static*/ const Reticulum& Reticulum::_instance = {Type::NONE};
+
+/*static*/ bool Reticulum::__transport_enabled = false;
+/*static*/ bool Reticulum::__link_mtu_discovery = RNS::Type::Reticulum::LINK_MTU_DISCOVERY;
+/*static*/ bool Reticulum::__remote_management_enabled = false;
+/*static*/ bool Reticulum::__use_implicit_proof = true;
+/*static*/ bool Reticulum::__allow_probes = false;
+/*static*/ bool Reticulum::panic_on_interface_error = false;
+
+#ifdef ARDUINO
+// Noise source to seed the random number generator.
+//TransistorNoiseSource noise(A1);
+#endif
+
+/*
+Initialises and starts a Reticulum instance. This must be
+done before any other operations, and Reticulum will not
+pass any traffic before being instantiated.
+
+:param configdir: Full path to a Reticulum configuration directory.
+*/
+
+/*p TODO
+@staticmethod
+void Reticulum::exit_handler():
+ # This exit handler is called whenever Reticulum is asked to
+ # shut down, and will in turn call exit handlers in other
+ # classes, saving necessary information to disk and carrying
+ # out cleanup operations.
+
+ Transport::exit_handler()
+ RNS.Identity.exit_handler()
+
+@staticmethod
+void Reticulum::sigint_handler(signal, frame):
+ Transport::detach_interfaces()
+ RNS.exit()
+
+
+@staticmethod
+void Reticulum::sigterm_handler(signal, frame):
+ Transport::detach_interfaces()
+ RNS.exit()
+*/
+
+//def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
+Reticulum::Reticulum() : _object(new Object()) {
+ MEM("Reticulum default object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+
+ // Initialize random number generator
+ TRACE("Initializing RNG...");
+ RNG.begin("Reticulum");
+ TRACE("RNG initial random value: " + std::to_string(Cryptography::randomnum()));
+
+#ifdef ARDUINO
+ // Add a noise source to the list of sources known to RNG.
+ //RNG.addNoiseSource(noise);
+ #endif
+
+ //z RNS.vendor.platformutils.platform_checks()
+
+/*p TODO
+ if configdir != None:
+ Reticulum.configdir = configdir
+ else:
+ if os.path.isdir("/etc/reticulum") and os.path.isfile("/etc/reticulum/config"):
+ Reticulum.configdir = "/etc/reticulum"
+ elif os.path.isdir(Reticulum.userdir+"/.config/reticulum") and os.path.isfile(Reticulum.userdir+"/.config/reticulum/config"):
+ Reticulum.configdir = Reticulum.userdir+"/.config/reticulum"
+ else:
+ Reticulum.configdir = Reticulum.userdir+"/.reticulum"
+
+ if logdest == RNS.LOG_FILE:
+ RNS.logdest = RNS.LOG_FILE
+ RNS.logfile = Reticulum.configdir+"/logfile"
+
+ Reticulum.configpath = Reticulum.configdir+"/config"
+ Reticulum.storagepath = Reticulum.configdir+"/storage"
+ Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
+ Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
+ Reticulum.identitypath = Reticulum.configdir+"/storage/identities"
+*/
+// CBA TEST
+#ifdef ARDUINO
+ //_storagepath = "";
+ strncpy(_storagepath, "", FILEPATH_MAXSIZE);
+ //_cachepath = "/cache";
+ strncpy(_cachepath, "/cache", FILEPATH_MAXSIZE);
+#else
+ //_storagepath = ".";
+ strncpy(_storagepath, ".", FILEPATH_MAXSIZE);
+ //_cachepath = "./cache";
+ strncpy(_cachepath, "./cache", FILEPATH_MAXSIZE);
+#endif
+
+#ifdef ARDUINO
+#if defined(RNS_USE_FS)
+ // load time offset from file if it exists
+ try {
+ char time_offset_path[FILEPATH_MAXSIZE];
+ snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
+ if (OS::file_exists(time_offset_path)) {
+ Bytes buf;
+ if (OS::read_file(time_offset_path, buf) == 8) {
+ uint64_t offset = *(uint64_t*)buf.data();
+ DEBUG("Read time offset of " + std::to_string(offset) + " from file");
+ OS::setTimeOffset(offset);
+ }
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Failed to load time offset, the contained exception was: %s", e.what());
+ }
+#endif
+#endif
+
+ // Initialize time-based variables *after* time offset update
+ _object->_last_data_persist = OS::time();
+ _object->_last_cache_clean = 0.0;
+ _object->_jobs_last_run = OS::time();
+
+/*p TODO
+ if not os.path.isdir(Reticulum.storagepath):
+ os.makedirs(Reticulum.storagepath)
+
+ if not os.path.isdir(Reticulum.cachepath):
+ os.makedirs(Reticulum.cachepath)
+
+ if not os.path.isdir(Reticulum.resourcepath):
+ os.makedirs(Reticulum.resourcepath)
+
+ if not os.path.isdir(Reticulum.identitypath):
+ os.makedirs(Reticulum.identitypath)
+
+ if os.path.isfile(self.configpath):
+ try:
+ self.config = ConfigObj(self.configpath)
+ except Exception as e:
+ RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
+ RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
+ RNS.panic()
+ else:
+ RNS.log("Could not load config file, creating default configuration file...")
+ self.__create_default_config()
+ RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.")
+ time.sleep(1.5)
+
+ self.__apply_config()
+ RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
+
+ RNS.Identity.load_known_destinations()
+*/
+
+ // CBA Moved to start() so Transport is not started until after interfaces are setup
+ //Transport::start(*this);
+
+/*p TODO
+ self.rpc_addr = ("127.0.0.1", self.local_control_port)
+ self.rpc_key = RNS.Identity.full_hash(Transport::identity.get_private_key())
+
+ if self.is_shared_instance:
+ self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, authkey=self.rpc_key)
+ thread = threading.Thread(target=self.rpc_loop)
+ thread.daemon = True
+ thread.start()
+
+ atexit.register(Reticulum.exit_handler)
+ signal.signal(signal.SIGINT, Reticulum.sigint_handler)
+ signal.signal(signal.SIGTERM, Reticulum.sigterm_handler)
+*/
+
+ MEM("Reticulum default object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+}
+
+void Reticulum::start() {
+
+ INFO("Total memory: " + std::to_string(OS::heap_size()));
+ INFO("Total flash: " + std::to_string(OS::storage_size()));
+
+ INFO("Starting Transport...");
+ Transport::start(*this);
+}
+
+void Reticulum::loop() {
+ assert(_object);
+ if (!_object->_is_connected_to_shared_instance) {
+
+ // Perform Reticulum housekeeping
+ if (OS::time() > (_object->_jobs_last_run + JOB_INTERVAL)) {
+ jobs();
+ _object->_jobs_last_run = OS::time();
+ }
+
+ // Perform interface processing
+ for (auto& [hash, interface] : Transport::get_interfaces()) {
+ interface.loop();
+ }
+
+ // Perform Filesystem processing
+ FileSystem& filesystem = OS::get_filesystem();
+ if (filesystem) {
+ filesystem.loop();
+ }
+
+
+ // Perform Transport processing
+ RNS::Transport::loop();
+ }
+ // Perform random number gnerator housekeeping
+ RNG.loop();
+}
+
+void Reticulum::jobs() {
+
+ double now = OS::time();
+
+#if 1
+ // CBA Detect low-memory condition and reset
+ if (OS::heap_size() > 0) {
+ uint8_t remaining = (uint8_t)((double)OS::heap_available() / (double)OS::heap_size() * 100.0);
+ if (remaining <= 2) {
+ head("DETECTED LOW-MEMORY CONDITION (" + std::to_string(remaining) + "%), RESETTING!!!", LOG_CRITICAL);
+ persist_data();
+#if defined(ESP32)
+ ESP.restart();
+#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
+ //dbgDumpMemory();
+ NVIC_SystemReset();
+#endif
+ }
+ }
+#endif
+
+ if (now > _object->_last_cache_clean + CLEAN_INTERVAL) {
+ clean_caches();
+ _object->_last_cache_clean = OS::time();
+ }
+
+ if (now > _object->_last_data_persist + PERSIST_INTERVAL) {
+ persist_data();
+ }
+}
+
+// CBA TODO
+/*
+void Reticulum::start_local_interface() {
+}
+
+void Reticulum::apply_config() {
+}
+
+void Reticulum::_add_interface(self,interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None):
+*/
+
+void Reticulum::should_persist_data() {
+ if (OS::time() > _object->_last_data_persist + GRACIOUS_PERSIST_INTERVAL) {
+ persist_data();
+ }
+}
+
+void Reticulum::persist_data() {
+ TRACE("Persisting transport and identity data...");
+ Transport::persist_data();
+ Identity::persist_data();
+
+#ifdef ARDUINO
+#if defined(RNS_USE_FS)
+ // write time offset to file
+ try {
+ char time_offset_path[FILEPATH_MAXSIZE];
+ snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
+ uint64_t offset = OS::ltime();
+ DEBUGF("Writing time offset of %llu to file %s", offset, time_offset_path);
+ Bytes buf((uint8_t*)&offset, sizeof(offset));
+ OS::write_file(time_offset_path, buf);
+ }
+ catch (std::exception& e) {
+ ERRORF("Failed to write time offset, the contained exception was: %s", e.what());
+ }
+#endif
+#endif
+
+ _object->_last_data_persist = OS::time();
+}
+
+void Reticulum::clean_caches() {
+ TRACE("Cleaning resource and packet caches...");
+ double now = OS::time();
+
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+/*
+ // Clean resource caches
+ for (auto& filename : OS::list_directory(resourcepath) {
+ try {
+ if (filename.length() == (Type::Identity::HASHLENGTH//8)*2) {
+ char filepath[FILEPATH_MAXSIZE];
+ snprintf(filepath, FILEPATH_MAXSIZE, "%s/%s", _resourcepath, filename.c_str());
+ //p mtime = os.path.getmtime(filepath)
+ //p age = now - mtime
+ //p if (age > Types::Reticulum.RESOURCE_CACHE) {
+ //p OS::remove_file(filepath);
+ //p }
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error while cleaning resources cache, the contained exception was: %s", e.what());
+ }
+ }
+
+ // Clean packet caches
+ for (auto& filename : OS::list_directory(_cachepath.c_str())) {
+ try {
+ if (filename.length() == (Type::Identity::HASHLENGTH/8)*2) {
+ char filepath[FILEPATH_MAXSIZE];
+ snprintf(filepath, FILEPATH_MAXSIZE, "%s/%s", _cachepath, filename.c_str());
+ //p mtime = os.path.getmtime(filepath)
+ //p age = now - mtime
+ //p if (age > Types::Transport::DESTINATION_TIMEOUT) {
+ //p OS::remove_file(filepath);
+ //p }
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error while cleaning packet cache, the contained exception was: %s", e.what());
+ }
+ }
+*/
+
+ Transport::clean_caches();
+
+ // CBA
+ Identity::cull_known_destinations();
+#endif
+
+}
+
+void Reticulum::clear_caches() {
+ TRACE("Clearing resource and packet caches...");
+
+ try {
+ char destination_table_path[FILEPATH_MAXSIZE];
+ snprintf(destination_table_path, FILEPATH_MAXSIZE, "%s/destination_table", _storagepath);
+ OS::remove_file(destination_table_path);
+
+ OS::remove_directory(_cachepath);
+
+#ifdef ARDUINO
+ char time_offset_path[FILEPATH_MAXSIZE];
+ snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
+ OS::remove_file(time_offset_path);
+#endif
+ }
+ catch (std::exception& e) {
+ ERRORF("Failed to clear cache file(s), the contained exception was: %s", e.what());
+ }
+}
+
+/*p TODO
+
+void Reticulum::__create_default_config() {
+ self.config = ConfigObj(__default_rns_config__)
+ self.config.filename = Reticulum.configpath
+
+ if not os.path.isdir(Reticulum.configdir):
+ os.makedirs(Reticulum.configdir)
+ self.config.write()
+}
+
+void Reticulum::rpc_loop() {
+}
+
+void Reticulum::get_interface_stats() const {
+}
+*/
+
+const std::map<Bytes, Transport::DestinationEntry>& Reticulum::get_path_table() const {
+/*
+ path_table = []
+ for dst_hash in Transport::destination_table:
+ entry = {
+ "hash": dst_hash,
+ "timestamp": Transport::destination_table[dst_hash][0],
+ "via": Transport::destination_table[dst_hash][1],
+ "hops": Transport::destination_table[dst_hash][2],
+ "expires": Transport::destination_table[dst_hash][3],
+ "interface": str(Transport::destination_table[dst_hash][5]),
+ }
+ path_table.append(entry)
+
+ return path_table
+*/
+ return Transport::get_destination_table();
+}
+
+const std::map<Bytes, Transport::RateEntry>& Reticulum::get_rate_table() const {
+/*
+ rate_table = []
+ for dst_hash in Transport::announce_rate_table:
+ entry = {
+ "hash": dst_hash,
+ "last": Transport::announce_rate_table[dst_hash]["last"],
+ "rate_violations": Transport::announce_rate_table[dst_hash]["rate_violations"],
+ "blocked_until": Transport::announce_rate_table[dst_hash]["blocked_until"],
+ "timestamps": Transport::announce_rate_table[dst_hash]["timestamps"],
+ }
+ rate_table.append(entry)
+
+ return rate_table
+*/
+ return Transport::get_announce_rate_table();
+}
+
+bool Reticulum::drop_path(const Bytes& destination) {
+ return Transport::expire_path(destination);
+}
+
+uint16_t Reticulum::drop_all_via(const Bytes& transport_hash) {
+ uint16_t dropped_count = 0;
+ //for (auto& destination_hash : Transport::get_destination_table()) {
+ for (const auto& [destination_hash, destination_entry] : Transport::get_destination_table()) {
+ if (destination_entry._received_from == transport_hash) {
+ Transport::expire_path(destination_hash);
+ ++dropped_count;
+ }
+ }
+ return dropped_count;
+}
+
+void Reticulum::drop_announce_queues() {
+ Transport::drop_announce_queues();
+}
+
+const std::string Reticulum::get_next_hop_if_name(const Bytes& destination) const {
+ return Transport::next_hop_interface(destination).name();
+}
+
+double Reticulum::get_first_hop_timeout(const Bytes& destination) const {
+ return Transport::first_hop_timeout(destination);
+}
+
+const Bytes Reticulum::get_next_hop(const Bytes& destination) const {
+ return Transport::next_hop(destination);
+}
+
+size_t Reticulum::get_link_count() const {
+ return Transport::get_link_table().size();
+}
+
+/*p
+void Reticulum::get_packet_rssi(const Bytes& packet_hash) const {
+ for entry in Transport::local_client_rssi_cache:
+ if entry[0] == packet_hash:
+ return entry[1]
+
+ return None
+
+void Reticulum::get_packet_snr(const Bytes& packet_hash) const {
+ for entry in Transport::local_client_snr_cache:
+ if entry[0] == packet_hash:
+ return entry[1]
+
+ return None
+
+void Reticulum::get_packet_q(const Bytes& packet_hash) const {
+ for entry in Transport::local_client_q_cache:
+ if entry[0] == packet_hash:
+ return entry[1]
+
+ return None
+*/

diff --git a/lib/microReticulum/src/Reticulum.h b/lib/microReticulum/src/Reticulum.h
new file mode 100755
index 0000000..6cd12af
--- /dev/null
+++ b/lib/microReticulum/src/Reticulum.h
@@ -0,0 +1,182 @@
+#pragma once
+
+#include "Transport.h"
+#include "Log.h"
+#include "Type.h"
+#include "Utilities/OS.h"
+
+#include <vector>
+#include <map>
+#include <string>
+#include <memory>
+#include <cassert>
+#include <stdint.h>
+
+namespace RNS {
+
+ class Reticulum {
+
+ public:
+
+ //z router = None
+ //z config = None
+
+ // The default configuration path will be expanded to a directory
+ // named ".reticulum" inside the current users home directory
+ //z userdir = os.path.expanduser("~")
+ //z configdir = None
+ //z configpath = ""
+ //p storagepath = ""
+ //static std::string _storagepath;
+ static char _storagepath[Type::Reticulum::FILEPATH_MAXSIZE];
+ //p cachepath = ""
+ //static std::string _cachepath;
+ static char _cachepath[Type::Reticulum::FILEPATH_MAXSIZE];
+
+ static const Reticulum& _instance;
+
+ static bool __transport_enabled;
+ static bool __link_mtu_discovery;
+ static bool __remote_management_enabled;
+ static bool __use_implicit_proof;
+ static bool __allow_probes;
+ static bool panic_on_interface_error;
+
+ public:
+ // Return the currently running Reticulum instance
+ inline static const Reticulum& get_instance() { return _instance; }
+
+ public:
+ Reticulum();
+ Reticulum(Type::NoneConstructor none) {
+ MEM("Reticulum empty object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+ Reticulum(const Reticulum& reticulum) : _object(reticulum._object) {
+ MEM("Reticulum object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+ virtual ~Reticulum() {
+ MEM("Reticulum object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ }
+
+ inline Reticulum& operator = (const Reticulum& reticulum) {
+ _object = reticulum._object;
+ MEM("Reticulum object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
+ return *this;
+ }
+ inline operator bool() const {
+ return _object.get() != nullptr;
+ }
+ inline bool operator < (const Reticulum& reticulum) const {
+ return _object.get() < reticulum._object.get();
+ }
+
+ public:
+ void start();
+ void loop();
+ void jobs();
+ void should_persist_data();
+ void persist_data();
+ void clean_caches();
+ void clear_caches();
+ //void __create_default_config();
+ //void rpc_loop();
+ //void get_interface_stats() const;
+ const std::map<Bytes, Transport::DestinationEntry>& get_path_table() const;
+ const std::map<Bytes, Transport::RateEntry>& get_rate_table() const;
+ bool drop_path(const Bytes& destination);
+ uint16_t drop_all_via(const Bytes& transport_hash);
+ void drop_announce_queues();
+ const std::string get_next_hop_if_name(const Bytes& destination) const;
+ double get_first_hop_timeout(const Bytes& destination) const;
+ const Bytes get_next_hop(const Bytes& destination) const;
+ size_t get_link_count() const;
+ //void get_packet_rssi(const Bytes& packet_hash) const;
+ //void get_packet_snr(const Bytes& packet_hash) const;
+ //void get_packet_q(const Bytes& packet_hash) const;
+
+ /*
+ Returns whether proofs sent are explicit or implicit.
+
+ :returns: True if the current running configuration specifies to use implicit proofs. False if not.
+ */
+ inline static bool should_use_implicit_proof() { return __use_implicit_proof; }
+
+ /*
+ Returns whether Transport is enabled for the running
+ instance.
+
+ When Transport is enabled, Reticulum will
+ route traffic for other peers, respond to path requests
+ and pass announces over the network.
+
+ :returns: True if Transport is enabled, False if not.
+ */
+ inline static bool transport_enabled() { return __transport_enabled; }
+ inline static void transport_enabled(bool transport_enabled) { __transport_enabled = transport_enabled; }
+
+ /*
+ Returns whether link MTU discovery is enabled for the running
+ instance.
+
+ When link MTU discovery is enabled, Reticulum will
+ automatically upgrade link MTUs to the highest supported
+ value, increasing transfer speed and efficiency.
+
+ :returns: True if link MTU discovery is enabled, False if not.
+ */
+ inline static bool link_mtu_discovery() { return __link_mtu_discovery; }
+ inline static void link_mtu_discovery(bool link_mtu_discovery) { __link_mtu_discovery = link_mtu_discovery; }
+
+ /*
+ Returns whether remote management is enabled for the
+ running instance.
+
+ When remote management is enabled, authenticated peers
+ can remotely query and manage this instance.
+
+ :returns: True if remote management is enabled, False if not.
+ */
+ inline static bool remote_management_enabled() { return __remote_management_enabled; }
+ inline static void remote_management_enabled(bool remote_management_enabled) { __remote_management_enabled = remote_management_enabled; }
+
+ inline static bool probe_destination_enabled() { return __allow_probes; }
+ inline static void probe_destination_enabled(bool allow_probes) { __allow_probes = allow_probes; }
+
+ // getters/setters
+ inline bool is_connected_to_shared_instance() const { assert(_object); return _object->_is_connected_to_shared_instance; }
+
+ private:
+ class Object {
+ public:
+ Object() {
+ MEM("Reticulum data object created, this: " + std::to_string((uintptr_t)this));
+ }
+ virtual ~Object() {
+ MEM("Reticulum data object destroyed, this: " + std::to_string((uintptr_t)this));
+ }
+ private:
+
+ uint16_t _local_interface_port = 37428;
+ uint16_t _local_control_port = 37429;
+ bool _share_instance = true;
+ //p _rpc_listener = None
+
+ //p _ifac_salt = Reticulum.IFAC_SALT
+
+ bool _is_shared_instance = false;
+ bool _is_connected_to_shared_instance = false;
+ bool _is_standalone_instance = false;
+ //p _jobs_thread = None
+ double _last_data_persist = Utilities::OS::time();
+ double _last_cache_clean = 0.0;
+
+ // CBA
+ double _jobs_last_run = Utilities::OS::time();
+
+ friend class Reticulum;
+ };
+ std::shared_ptr<Object> _object;
+
+ };
+
+}

diff --git a/lib/microReticulum/src/Transport.cpp b/lib/microReticulum/src/Transport.cpp
new file mode 100755
index 0000000..13ad1a0
--- /dev/null
+++ b/lib/microReticulum/src/Transport.cpp
@@ -0,0 +1,4305 @@
+#include "Transport.h"
+#include "Link.h"
+
+#include "Reticulum.h"
+#include "Destination.h"
+#include "Identity.h"
+#include "Packet.h"
+#include "Interface.h"
+#include "Log.h"
+#include "Cryptography/Random.h"
+#include "Utilities/OS.h"
+#include "Utilities/Persistence.h"
+
+#include <algorithm>
+#include <unistd.h>
+#include <time.h>
+
+using namespace RNS;
+using namespace RNS::Type::Transport;
+using namespace RNS::Utilities;
+
+#if defined(INTERFACES_SET)
+///*static*/ std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> Transport::_interfaces;
+/*static*/ std::set<std::reference_wrapper<Interface>, std::less<Interface>> Transport::_interfaces;
+#elif defined(INTERFACES_LIST)
+/*static*/ std::list<std::reference_wrapper<Interface>> Transport::_interfaces;
+#elif defined(INTERFACES_MAP)
+/*static*/ std::map<Bytes, Interface&> Transport::_interfaces;
+#endif
+#if defined(DESTINATIONS_SET)
+/*static*/ std::set<Destination> Transport::_destinations;
+#elif defined(DESTINATIONS_MAP)
+/*static*/ std::map<Bytes, Destination> Transport::_destinations;
+#endif
+/*static*/ std::set<Link> Transport::_pending_links;
+/*static*/ std::set<Link> Transport::_active_links;
+/*static*/ std::set<Bytes> Transport::_packet_hashlist;
+/*static*/ std::list<PacketReceipt> Transport::_receipts;
+
+/*static*/ std::map<Bytes, Transport::AnnounceEntry> Transport::_announce_table;
+/*static*/ std::map<Bytes, Transport::DestinationEntry> Transport::_destination_table;
+/*static*/ std::map<Bytes, Transport::ReverseEntry> Transport::_reverse_table;
+/*static*/ std::map<Bytes, Transport::LinkEntry> Transport::_link_table;
+/*static*/ std::map<Bytes, Transport::AnnounceEntry> Transport::_held_announces;
+/*static*/ std::set<HAnnounceHandler> Transport::_announce_handlers;
+/*static*/ std::map<Bytes, Transport::TunnelEntry> Transport::_tunnels;
+/*static*/ std::map<Bytes, Transport::RateEntry> Transport::_announce_rate_table;
+/*static*/ std::map<Bytes, double> Transport::_path_requests;
+
+/*static*/ std::map<Bytes, Transport::PathRequestEntry> Transport::_discovery_path_requests;
+/*static*/ std::set<Bytes> Transport::_discovery_pr_tags;
+
+/*static*/ std::set<Destination> Transport::_control_destinations;
+/*static*/ std::set<Bytes> Transport::_control_hashes;
+
+///*static*/ std::set<Interface> Transport::_local_client_interfaces;
+/*static*/ std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> Transport::_local_client_interfaces;
+
+/*static*/ std::map<Bytes, const Interface&> Transport::_pending_local_path_requests;
+
+// CBA
+/*static*/ std::map<Bytes, Transport::PacketEntry> Transport::_packet_table;
+
+/*static*/ uint16_t Transport::_LOCAL_CLIENT_CACHE_MAXSIZE = 512;
+
+/*static*/ double Transport::_start_time = 0.0;
+/*static*/ bool Transport::_jobs_locked = false;
+/*static*/ bool Transport::_jobs_running = false;
+/*static*/ float Transport::_job_interval = 0.250;
+/*static*/ double Transport::_jobs_last_run = 0.0;
+/*static*/ double Transport::_links_last_checked = 0.0;
+/*static*/ float Transport::_links_check_interval = 1.0;
+/*static*/ double Transport::_receipts_last_checked = 0.0;
+/*static*/ float Transport::_receipts_check_interval = 1.0;
+/*static*/ double Transport::_announces_last_checked = 0.0;
+/*static*/ float Transport::_announces_check_interval = 1.0;
+/*static*/ double Transport::_tables_last_culled = 0.0;
+// CBA MCU
+/*static*/ //float Transport::_tables_cull_interval = 5.0;
+/*static*/ float Transport::_tables_cull_interval = 60.0;
+/*static*/ bool Transport::_saving_path_table = false;
+// CBA ACCUMULATES
+// CBA MCU
+/*static*/ //uint16_t Transport::_hashlist_maxsize = 1000000;
+/*static*/ //uint16_t Transport::_hashlist_maxsize = 100;
+/*static*/ uint16_t Transport::_hashlist_maxsize = 100;
+// CBA ACCUMULATES
+// CBA MCU
+/*static*/ //uint16_t Transport::_max_pr_tags = 32000;
+/*static*/ uint16_t Transport::_max_pr_tags = 32;
+
+// CBA
+// CBA ACCUMULATES
+/*static*/ uint16_t Transport::_path_table_maxsize = 100;
+// CBA ACCUMULATES
+/*static*/ uint16_t Transport::_path_table_maxpersist = 100;
+/*static*/ double Transport::_last_saved = 0.0;
+/*static*/ float Transport::_save_interval = 3600.0;
+/*static*/ uint32_t Transport::_destination_table_crc = 0;
+
+/*static*/ Reticulum Transport::_owner({Type::NONE});
+
+// BOUNDARY MODE Whitelist 1: addresses of local devices (from LoRa and LocalTCP interfaces)
+static std::set<Bytes> _boundary_local_addresses;
+// BOUNDARY MODE Whitelist 2: addresses mentioned in packets from local devices
+static std::set<Bytes> _boundary_mentioned_addresses;
+
+// BOUNDARY MODE: Check if an interface is the backbone
+static bool is_backbone_interface(const Interface& iface) {
+ return iface.is_backbone();
+}
+/*static*/ Identity Transport::_identity({Type::NONE});
+
+// CBA
+/*static*/ Transport::Callbacks Transport::_callbacks;
+
+// CBA Stats
+/*static*/ uint32_t Transport::_packets_sent = 0;
+/*static*/ uint32_t Transport::_packets_received = 0;
+/*static*/ uint32_t Transport::_destinations_added = 0;
+/*static*/ size_t Transport::_last_memory = 0;
+/*static*/ size_t Transport::_last_flash = 0;
+
+/*static*/ void Transport::start(const Reticulum& reticulum_instance) {
+ INFO("Transport starting...");
+ _jobs_running = true;
+ _owner = reticulum_instance;
+
+ // Initialize time-based variables *after* time offset update
+ _jobs_last_run = OS::time();
+ _links_last_checked = OS::time();
+ _receipts_last_checked = OS::time();
+ _announces_last_checked = OS::time();
+ _tables_last_culled = OS::time();
+ _last_saved = OS::time();
+
+ // ensure required directories exist
+ if (!OS::directory_exists(Reticulum::_cachepath)) {
+ VERBOSE("No cache directory, creating...");
+ OS::create_directory(Reticulum::_cachepath);
+ }
+
+ if (!_identity) {
+ char transport_identity_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(transport_identity_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/transport_identity", Reticulum::_storagepath);
+ DEBUG("Checking for transport identity...");
+ try {
+ if (OS::file_exists(transport_identity_path)) {
+ _identity = Identity::from_file(transport_identity_path);
+ }
+
+ if (!_identity) {
+ VERBOSE("No valid Transport Identity in storage, creating...");
+ _identity = Identity();
+ _identity.to_file(transport_identity_path);
+ }
+ else {
+ VERBOSE("Loaded Transport Identity from storage");
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Failed to check for transport identity, the contained exception was: %s", e.what());
+ }
+ }
+
+// TODO
+/*
+ packet_hashlist_path = Reticulum::storagepath + "/packet_hashlist";
+ if (!owner.is_connected_to_shared_instance()) {
+ if (os.path.isfile(packet_hashlist_path)) {
+ try {
+ //p file = open(packet_hashlist_path, "rb")
+ //p Transport.packet_hashlist = umsgpack.unpackb(file.read())
+ //p file.close()
+ }
+ catch (std::exception& e) {
+ ERRORF("Could not load packet hashlist from storage, the contained exception was: %s", e.what());
+ }
+ }
+ }
+*/
+
+ // Create transport-specific destination for path request
+ Destination path_request_destination({Type::NONE}, Type::Destination::IN, Type::Destination::PLAIN, APP_NAME, "path.request");
+ path_request_destination.set_packet_callback(path_request_handler);
+ // CBA ACCUMULATES
+ _control_destinations.insert(path_request_destination);
+ // CBA ACCUMULATES
+ _control_hashes.insert(path_request_destination.hash());
+ DEBUG("Created transport-specific path request destination " + path_request_destination.hash().toHex());
+
+ // Create transport-specific destination for tunnel synthesize
+ Destination tunnel_synthesize_destination({Type::NONE}, Type::Destination::IN, Type::Destination::PLAIN, APP_NAME, "tunnel.synthesize");
+ tunnel_synthesize_destination.set_packet_callback(tunnel_synthesize_handler);
+ // CBA BUG?
+ //p Transport.control_destinations.append(Transport.tunnel_synthesize_handler)
+ // CBA ACCUMULATES
+ _control_destinations.insert(tunnel_synthesize_destination);
+ // CBA ACCUMULATES
+ _control_hashes.insert(tunnel_synthesize_destination.hash());
+ DEBUG("Created transport-specific tunnel synthesize destination " + tunnel_synthesize_destination.hash().toHex());
+
+ _jobs_running = false;
+
+ // CBA Threading
+ //p thread = threading.Thread(target=Transport.jobloop, daemon=True)
+ //p thread.start()
+
+ if (Reticulum::transport_enabled()) {
+ INFO("Transport mode is enabled");
+
+ // Read in path table and then write and clean in case any entries are invalid
+ read_path_table();
+ DEBUG("Writing path table and cleaning caches to clean-up any orphaned paths/files");
+ write_path_table();
+ clean_caches();
+
+ read_tunnel_table();
+
+ // Create transport-specific destination for probe requests
+ if (Reticulum::probe_destination_enabled()) {
+ Destination probe_destination(_identity, Type::Destination::IN, Type::Destination::SINGLE, APP_NAME, "probe");
+ probe_destination.accepts_links(false);
+ probe_destination.set_proof_strategy(Type::Destination::PROVE_ALL);
+ DEBUG("Created probe responder destination " + probe_destination.hash().toHex());
+ probe_destination.announce();
+ NOTICE("Transport Instance will respond to probe requests on " + probe_destination.toString());
+ }
+
+ VERBOSE("Transport instance " + _identity.toString() + " started");
+ _start_time = OS::time();
+ }
+
+// TODO
+/*p
+ // Synthesize tunnels for any interfaces wanting it
+ for interface in Transport.interfaces:
+ interface.tunnel_id = None
+ if hasattr(interface, "wants_tunnel") and interface.wants_tunnel:
+ Transport.synthesize_tunnel(interface)
+*/
+
+//#ifndef NDEBUG
+ // CBA DEBUG
+ dump_stats();
+//#endif
+}
+
+/*static*/ void Transport::loop() {
+ if (OS::time() > (_jobs_last_run + _job_interval)) {
+ jobs();
+ _jobs_last_run = OS::time();
+ }
+}
+
+/*static*/ void Transport::jobs() {
+ //TRACE("Transport::jobs()");
+
+ std::vector<Packet> outgoing;
+ std::set<Bytes> path_requests;
+ int count;
+ _jobs_running = true;
+
+ try {
+ if (!_jobs_locked) {
+
+ // Process active and pending link lists
+ if (OS::time() > (_links_last_checked + _links_check_interval)) {
+ std::set<Link> pending_links(_pending_links);
+ for (auto& link : pending_links) {
+ if (link.status() == Type::Link::CLOSED) {
+ // If we are not a Transport Instance, finding a pending link
+ // that was never activated will trigger an expiry of the path
+ // to the destination, and an attempt to rediscover the path.
+ if (!Reticulum::transport_enabled()) {
+ expire_path(link.destination().hash());
+
+ // If we are connected to a shared instance, it will take
+ // care of sending out a new path request. If not, we will
+ // send one directly.
+ if (!_owner.is_connected_to_shared_instance()) {
+ double last_path_request = 0;
+ auto iter = _path_requests.find(link.destination().hash());
+ if (iter != _path_requests.end()) {
+ last_path_request = (*iter).second;
+ }
+
+ if ((OS::time() - last_path_request) > Type::Transport::PATH_REQUEST_MI) {
+ DEBUG("Trying to rediscover path for " + link.destination().hash().toHex() + " since an attempted link was never established");
+ //if (path_requests.find(link.destination().hash()) == path_requests.end()) {
+ if (path_requests.count(link.destination().hash()) == 0) {
+ // CBA ACCUMULATES
+ path_requests.insert(link.destination().hash());
+ }
+ }
+ }
+ }
+
+ _pending_links.erase(link);
+ }
+ }
+ std::set<Link> active_links(_active_links);
+ for (auto& link : active_links) {
+ if (link.status() == Type::Link::CLOSED) {
+ _active_links.erase(link);
+ }
+ }
+
+ _links_last_checked = OS::time();
+ }
+
+ // Process receipts list for timed-out packets
+ if (OS::time() > (_receipts_last_checked + _receipts_check_interval)) {
+ while (_receipts.size() > Type::Transport::MAX_RECEIPTS) {
+ //p culled_receipt = Transport.receipts.pop(0)
+ PacketReceipt culled_receipt = _receipts.front();
+ _receipts.pop_front();
+ culled_receipt.set_timeout(-1);
+ culled_receipt.check_timeout();
+ }
+
+ std::list<PacketReceipt> cull_receipts;
+ for (auto& receipt : _receipts) {
+ receipt.check_timeout();
+ if (receipt.status() != Type::PacketReceipt::SENT) {
+ //p if receipt in Transport.receipts:
+ //p Transport.receipts.remove(receipt)
+ cull_receipts.push_back(receipt);
+ }
+ }
+ // CBA since modifying of collection while iterating is forbidden
+ for (auto& receipt : _receipts) {
+ cull_receipts.remove(receipt);
+ }
+
+ _receipts_last_checked = OS::time();
+ }
+
+ // Process announces needing retransmission
+ if (OS::time() > (_announces_last_checked + _announces_check_interval)) {
+ //p for destination_hash in Transport.announce_table:
+ for (auto& [destination_hash, announce_entry] : _announce_table) {
+ //for (auto& pair : _announce_table) {
+ // const auto& destination_hash = pair.first;
+ // auto& announce_entry = pair.second;
+//TRACE("[0] announce entry data size: " + std::to_string(announce_entry._packet.data().size()));
+ //p announce_entry = Transport.announce_table[destination_hash]
+ if (announce_entry._retries > 0 && announce_entry._retries >= Type::Transport::LOCAL_REBROADCASTS_MAX) {
+ TRACE("Completed announce processing for " + destination_hash.toHex() + ", local rebroadcast limit reached");
+ // CBA OK to modify collection here since we're immediately exiting iteration
+ _announce_table.erase(destination_hash);
+ break;
+ }
+ else if (announce_entry._retries > Type::Transport::PATHFINDER_R) {
+ TRACE("Completed announce processing for " + destination_hash.toHex() + ", retry limit reached");
+ // CBA OK to modify collection here since we're immediately exiting iteration
+ _announce_table.erase(destination_hash);
+ break;
+ }
+ else {
+ if (OS::time() > announce_entry._retransmit_timeout) {
+ TRACE("Performing announce processing for " + destination_hash.toHex() + "...");
+ announce_entry._retransmit_timeout = OS::time() + Type::Transport::PATHFINDER_G + Type::Transport::PATHFINDER_RW;
+ announce_entry._retries += 1;
+ //p packet = announce_entry[5]
+ //p block_rebroadcasts = announce_entry[7]
+ //p attached_interface = announce_entry[8]
+ Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
+ if (announce_entry._block_rebroadcasts) {
+ announce_context = Type::Packet::PATH_RESPONSE;
+ }
+ //p announce_data = packet.data
+ Identity announce_identity(Identity::recall(announce_entry._packet.destination_hash()));
+ //Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown");
+ //announce_destination.hash(announce_entry._packet.destination_hash());
+ Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, announce_entry._packet.destination_hash());
+ //P announce_destination.hexhash = announce_destination.hash.hex()
+
+//if (announce_entry._attached_interface) {
+//TRACE("[1] interface is valid");
+//TRACE("[1] interface: " + announce_entry._attached_interface.debugString());
+//TRACE("[1] interface: " + announce_entry._attached_interface.toString());
+//}
+ Packet new_packet(
+ announce_destination,
+ //{Type::NONE},
+ announce_entry._attached_interface,
+ //{Type::NONE},
+ announce_entry._packet.data(),
+ Type::Packet::ANNOUNCE,
+ announce_context,
+ Type::Transport::TRANSPORT,
+ Type::Packet::HEADER_2,
+ Transport::_identity.hash(),
+ true,
+ announce_entry._packet.context_flag()
+ );
+
+ new_packet.hops(announce_entry._hops);
+ if (announce_entry._block_rebroadcasts) {
+ DEBUG("Rebroadcasting announce as path response for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
+ }
+ else {
+ DEBUG("Rebroadcasting announce for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
+ }
+
+ outgoing.push_back(new_packet);
+
+ // This handles an edge case where a peer sends a past
+ // request for a destination just after an announce for
+ // said destination has arrived, but before it has been
+ // rebroadcast locally. In such a case the actual announce
+ // is temporarily held, and then reinserted when the path
+ // request has been served to the peer.
+ //p if destination_hash in Transport.held_announces:
+ auto iter =_held_announces.find(destination_hash);
+ if (iter != _held_announces.end()) {
+ //p held_entry = Transport.held_announces.pop(destination_hash)
+ auto held_entry = (*iter).second;
+ _held_announces.erase(iter);
+ //p Transport.announce_table[destination_hash] = held_entry
+ //_announce_table[destination_hash] = held_entry;
+ //_announce_table.insert_or_assign({destination_hash, held_entry});
+ _announce_table.erase(destination_hash);
+ // CBA ACCUMULATES
+ _announce_table.insert({destination_hash, held_entry});
+ DEBUG("Reinserting held announce into table");
+ // CBA Must break after erase to avoid iterator invalidation
+ // (same pattern as the other two erases above in this loop)
+ break;
+ }
+ }
+ }
+ }
+
+ _announces_last_checked = OS::time();
+ }
+
+ // Cull the packet hashlist if it has reached its max size
+ if (_packet_hashlist.size() > _hashlist_maxsize) {
+ std::set<Bytes>::iterator iter = _packet_hashlist.begin();
+ std::advance(iter, _packet_hashlist.size() - _hashlist_maxsize);
+ _packet_hashlist.erase(_packet_hashlist.begin(), iter);
+ }
+
+ // Cull the path request tags list if it has reached its max size
+ if (_discovery_pr_tags.size() > _max_pr_tags) {
+ std::set<Bytes>::iterator iter = _discovery_pr_tags.begin();
+ std::advance(iter, _discovery_pr_tags.size() - _max_pr_tags);
+ _discovery_pr_tags.erase(_discovery_pr_tags.begin(), iter);
+ }
+
+ if (OS::time() > (_tables_last_culled + _tables_cull_interval)) {
+
+ // CBA Disabled following since we're calling immediately after adding to path table now
+ // Cull the path table if it has reached its max size
+ //cull_path_table();
+
+ // Cull the reverse table according to timeout
+ std::vector<Bytes> stale_reverse_entries;
+ for (const auto& [packet_hash, reverse_entry] : _reverse_table) {
+ if (OS::time() > (reverse_entry._timestamp + REVERSE_TIMEOUT)) {
+ stale_reverse_entries.push_back(packet_hash);
+ }
+ }
+
+ // Cull the link table according to timeout
+ std::vector<Bytes> stale_links;
+ for (const auto& [link_id, link_entry] : _link_table) {
+ if (link_entry._validated) {
+ if (OS::time() > (link_entry._timestamp + LINK_TIMEOUT)) {
+ stale_links.push_back(link_id);
+ }
+ }
+ else {
+ if (OS::time() > link_entry._proof_timeout) {
+ stale_links.push_back(link_id);
+
+ double last_path_request = 0.0;
+ const auto& iter = _path_requests.find(link_entry._destination_hash);
+ if (iter != _path_requests.end()) {
+ last_path_request = (*iter).second;
+ }
+
+ uint8_t lr_taken_hops = link_entry._hops;
+
+ bool path_request_throttle = (OS::time() - last_path_request) < PATH_REQUEST_MI;
+ bool path_request_conditions = false;
+
+ // If the path has been invalidated between the time of
+ // making the link request and now, try to rediscover it
+ if (!has_path(link_entry._destination_hash)) {
+ DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and path is now missing");
+ path_request_conditions = true;
+ }
+
+ // If this link request was originated from a local client
+ // attempt to rediscover a path to the destination, if this
+ // has not already happened recently.
+ else if (!path_request_throttle && lr_taken_hops == 0) {
+ DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted local client link was never established");
+ path_request_conditions = true;
+ }
+
+ // If the link destination was previously only 1 hop
+ // away, this likely means that it was local to one
+ // of our interfaces, and that it roamed somewhere else.
+ // In that case, try to discover a new path.
+ else if (!path_request_throttle && hops_to(link_entry._destination_hash) == 1) {
+ DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and destination was previously local to an interface on this instance");
+ path_request_conditions = true;
+ }
+
+ // If the link destination was previously only 1 hop
+ // away, this likely means that it was local to one
+ // of our interfaces, and that it roamed somewhere else.
+ // In that case, try to discover a new path.
+ else if ( !path_request_throttle and lr_taken_hops == 1) {
+ DEBUG("Trying to rediscover path for " + link_entry._destination_hash.toHex() + " since an attempted link was never established, and link initiator is local to an interface on this instance");
+ path_request_conditions = true;
+ }
+
+ if (path_request_conditions) {
+ if (path_requests.count(link_entry._destination_hash) == 0) {
+ // CBA ACCUMULATES
+ path_requests.insert(link_entry._destination_hash);
+ }
+
+ if (!Reticulum::transport_enabled()) {
+ // Drop current path if we are not a transport instance, to
+ // allow using higher-hop count paths or reused announces
+ // from newly adjacent transport instances.
+ expire_path(link_entry._destination_hash);
+ }
+ }
+ }
+ }
+ }
+
+ // Cull the path table
+ std::vector<Bytes> stale_paths;
+ for (const auto& [destination_hash, destination_entry] : _destination_table) {
+ const Interface& attached_interface = destination_entry.receiving_interface();
+ double destination_expiry;
+ if (attached_interface && attached_interface.mode() == Type::Interface::MODE_ACCESS_POINT) {
+ destination_expiry = destination_entry._timestamp + AP_PATH_TIME;
+ }
+ else if (attached_interface && attached_interface.mode() == Type::Interface::MODE_ROAMING) {
+ destination_expiry = destination_entry._timestamp + ROAMING_PATH_TIME;
+ }
+ else {
+ destination_expiry = destination_entry._timestamp + DESTINATION_TIMEOUT;
+ }
+
+ if (OS::time() > destination_expiry) {
+ stale_paths.push_back(destination_hash);
+ DEBUG("Path to " + destination_hash.toHex() + " timed out and was removed");
+ }
+ else if (_interfaces.count(attached_interface.get_hash()) == 0) {
+ stale_paths.push_back(destination_hash);
+ DEBUG("Path to " + destination_hash.toHex() + " was removed since the attached interface no longer exists");
+ }
+ }
+
+ // Cull the pending discovery path requests table
+ std::vector<Bytes> stale_discovery_path_requests;
+ for (const auto& [destination_hash, path_entry] : _discovery_path_requests) {
+ if (OS::time() > path_entry._timeout) {
+ stale_discovery_path_requests.push_back(destination_hash);
+ DEBUG("Waiting path request for " + destination_hash.toString() + " timed out and was removed");
+ }
+ }
+
+ // Cull the tunnel table
+ count = 0;
+ std::vector<Bytes> stale_tunnels;
+ for (const auto& [tunnel_id, tunnel_entry] : _tunnels) {
+ if (OS::time() > tunnel_entry._expires) {
+ stale_tunnels.push_back(tunnel_id);
+ TRACE("Tunnel " + tunnel_id.toHex() + " timed out and was removed");
+ }
+ else {
+ std::vector<Bytes> stale_tunnel_paths;
+ for (const auto& [destination_hash, destination_entry] : tunnel_entry._serialised_paths) {
+ if (OS::time() > (destination_entry._timestamp + DESTINATION_TIMEOUT)) {
+ stale_tunnel_paths.push_back(destination_hash);
+ TRACE("Tunnel path to " + destination_hash.toHex() + " timed out and was removed");
+ }
+ }
+
+ //for (const auto& destination_hash : stale_tunnel_paths) {
+ for (const Bytes& destination_hash : stale_tunnel_paths) {
+ const_cast<TunnelEntry&>(tunnel_entry)._serialised_paths.erase(destination_hash);
+ ++count;
+ }
+ }
+ }
+ if (count > 0) {
+ TRACE("Removed " + std::to_string(count) + " tunnel paths");
+ }
+
+ remove_reverse_entries(stale_reverse_entries);
+ remove_links(stale_links);
+ remove_paths(stale_paths);
+ remove_discovery_path_requests(stale_discovery_path_requests);
+ remove_tunnels(stale_tunnels);
+
+//#ifndef NDEBUG
+ dump_stats();
+//#endif
+
+ _tables_last_culled = OS::time();
+ }
+
+ // CBA Periodically persist data
+ //if (OS::time() > (_last_saved + _save_interval)) {
+ // persist_data();
+ // _last_saved = OS::time();
+ //}
+ }
+ else {
+ // Transport jobs were locked, do nothing
+ //p pass
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("An exception occurred while running Transport jobs.");
+ ERRORF("The contained exception was: %s", e.what());
+ }
+
+ _jobs_running = false;
+
+ // CBA send announce retransmission packets
+ for (auto& packet : outgoing) {
+ packet.send();
+ }
+
+ // CBA send link-related path requests
+ for (auto& destination_hash : path_requests) {
+ request_path(destination_hash);
+ }
+}
+
+/*static*/ void Transport::transmit(Interface& interface, const Bytes& raw) {
+ TRACE("Transport::transmit()");
+ // CBA
+ if (_callbacks._transmit_packet) {
+ try {
+ _callbacks._transmit_packet(raw, interface);
+ }
+ catch (std::exception& e) {
+ DEBUG("Error while executing transmit packet callback. The contained exception was: " + std::string(e.what()));
+ }
+ }
+ try {
+ //if hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
+ if (interface.ifac_identity()) {
+// TODO
+/*p
+ // Calculate packet access code
+ ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:]
+
+ // Generate mask
+ mask = RNS.Cryptography.hkdf(
+ length=len(raw)+interface.ifac_size,
+ derive_from=ifac,
+ salt=interface.ifac_key,
+ context=None,
+ )
+
+ // Set IFAC flag
+ new_header = bytes([raw[0] | 0x80, raw[1]])
+
+ // Assemble new payload with IFAC
+ new_raw = new_header+ifac+raw[2:]
+
+ // Mask payload
+ i = 0; masked_raw = b""
+ for byte in new_raw:
+ if i == 0:
+ // Mask first header byte, but make sure the
+ // IFAC flag is still set
+ masked_raw += bytes([byte ^ mask[i] | 0x80])
+ elif i == 1 or i > interface.ifac_size+1:
+ // Mask second header byte and payload
+ masked_raw += bytes([byte ^ mask[i]])
+ else:
+ // Don't mask the IFAC itself
+ masked_raw += bytes([byte])
+ i += 1
+
+ // Send it
+ interface.on_outgoing(masked_raw)
+*/
+ }
+ else {
+ interface.send_outgoing(raw);
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error while transmitting on " + interface.toString() + ". The contained exception was: " + e.what());
+ }
+}
+
+/*static*/ bool Transport::outbound(Packet& packet) {
+ TRACE("Transport::outbound()");
+ ++_packets_sent;
+
+ if (!packet.destination()) {
+ //throw std::invalid_argument("Can not send packet with no destination.");
+ ERROR("Can not send packet with no destination");
+ return false;
+ }
+
+ TRACE("Transport::outbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops()));
+
+ while (_jobs_running) {
+ TRACE("Transport::outbound: sleeping...");
+ OS::sleep(0.0005);
+ }
+
+ _jobs_locked = true;
+
+ bool sent = false;
+ double outbound_time = OS::time();
+
+ // Check if we have a known path for the destination in the path table
+ //if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination.type != RNS.Destination.PLAIN and packet.destination.type != RNS.Destination.GROUP and packet.destination_hash in Transport.destination_table:
+ if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.destination().type() != Type::Destination::PLAIN && packet.destination().type() != Type::Destination::GROUP && _destination_table.find(packet.destination_hash()) != _destination_table.end()) {
+ TRACE("Transport::outbound: Path to destination is known");
+ //outbound_interface = Transport.destination_table[packet.destination_hash][5]
+ DestinationEntry destination_entry = (*_destination_table.find(packet.destination_hash())).second;
+ Interface outbound_interface = destination_entry.receiving_interface();
+
+ // If there's more than one hop to the destination, and we know
+ // a path, we insert the packet into transport by adding the next
+ // transport nodes address to the header, and modifying the flags.
+ // This rule applies both for "normal" transport, and when connected
+ // to a local shared Reticulum instance.
+ //if Transport.destination_table[packet.destination_hash][2] > 1:
+ if (destination_entry._hops > 1) {
+ TRACE("Forwarding packet to next closest interface...");
+ if (packet.header_type() == Type::Packet::HEADER_1) {
+ // Insert packet into transport
+ //new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
+ uint8_t new_flags = (Type::Packet::HEADER_2) << 6 | (Type::Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111);
+ // CBA RESERVE
+ //Bytes new_raw;
+ Bytes new_raw(512);
+ //new_raw = struct.pack("!B", new_flags)
+ new_raw << new_flags;
+ //new_raw += packet.raw[1:2]
+ new_raw << packet.raw().mid(1,1);
+ //new_raw += Transport.destination_table[packet.destination_hash][1]
+ new_raw << destination_entry._received_from;
+ //new_raw += packet.raw[2:]
+ new_raw << packet.raw().mid(2);
+ transmit(outbound_interface, new_raw);
+ //_destination_table[packet.destination_hash][0] = time.time()
+ destination_entry._timestamp = OS::time();
+ sent = true;
+ }
+ }
+
+ // In the special case where we are connected to a local shared
+ // Reticulum instance, and the destination is one hop away, we
+ // also add transport headers to inject the packet into transport
+ // via the shared instance. Normally a packet for a destination
+ // one hop away would just be broadcast directly, but since we
+ // are "behind" a shared instance, we need to get that instance
+ // to transport it onto the network.
+ //elif Transport.destination_table[packet.destination_hash][2] == 1 and Transport.owner.is_connected_to_shared_instance:
+ else if (destination_entry._hops == 1 && _owner.is_connected_to_shared_instance()) {
+ TRACE("Transport::outbound: Sending packet for directly connected interface to shared instance...");
+ if (packet.header_type() == Type::Packet::HEADER_1) {
+ // Insert packet into transport
+ //new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
+ uint8_t new_flags = (Type::Packet::HEADER_2) << 6 | (Type::Transport::TRANSPORT) << 4 | (packet.flags() & 0b00001111);
+ // CBA RESERVE
+ //Bytes new_raw;
+ Bytes new_raw(512);
+ //new_raw = struct.pack("!B", new_flags)
+ new_raw << new_flags;
+ //new_raw += packet.raw[1:2]
+ new_raw << packet.raw().mid(1, 1);
+ //new_raw += Transport.destination_table[packet.destination_hash][1]
+ new_raw << destination_entry._received_from;
+ //new_raw += packet.raw[2:]
+ new_raw << packet.raw().mid(2);
+ transmit(outbound_interface, new_raw);
+ //Transport.destination_table[packet.destination_hash][0] = time.time()
+ destination_entry._timestamp = OS::time();
+ sent = true;
+ }
+ }
+
+ // If none of the above applies, we know the destination is
+ // directly reachable, and also on which interface, so we
+ // simply transmit the packet directly on that one.
+ else {
+ TRACE("Transport::outbound: Sending packet over directly connected interface...");
+ transmit(outbound_interface, packet.raw());
+ sent = true;
+ }
+ }
+ // If we don't have a known path for the destination, we'll
+ // broadcast the packet on all outgoing interfaces, or the
+ // just the relevant interface if the packet has an attached
+ // interface, or belongs to a link.
+ else {
+ TRACE("Transport::outbound: Path to destination is unknown");
+ bool stored_hash = false;
+#if defined(INTERFACES_SET)
+ for (const Interface& interface : _interfaces) {
+#elif defined(INTERFACES_LIST)
+ for (Interface& interface : _interfaces) {
+#elif defined(INTERFACES_MAP)
+ for (auto& [hash, interface] : _interfaces) {
+#endif
+ TRACE("Transport::outbound: Checking interface " + interface.toString());
+ if (interface.OUT()) {
+ bool should_transmit = true;
+
+ if (packet.destination().type() == Type::Destination::LINK) {
+ if (!packet.destination_link()) throw std::invalid_argument("Packet is not associated with a Link");
+ if (packet.destination_link().status() == Type::Link::CLOSED) {
+ TRACE("Transport::outbound: Pscket destination is link-closed, not transmitting");
+ should_transmit = false;
+ }
+ // CBA Bug? Destination has no member attached_interface
+ //z if (interface != packet.destination().attached_interface()) {
+ //z should_transmit = false;
+ //z }
+ }
+
+ if (packet.attached_interface() && interface != packet.attached_interface()) {
+ TRACE("Transport::outbound: Packet has wrong attached interface, not transmitting");
+ should_transmit = false;
+ }
+
+ if (packet.packet_type() == Type::Packet::ANNOUNCE) {
+ if (!packet.attached_interface()) {
+ TRACE("Transport::outbound: Packet has no attached interface");
+ if (interface.mode() == Type::Interface::MODE_ACCESS_POINT) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " due to AP mode");
+ should_transmit = false;
+ }
+ else if (interface.mode() == Type::Interface::MODE_ROAMING) {
+ //local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
+ //Destination local_destination({Type::NONE});
+#if defined(DESTINATIONS_SET)
+ bool found_local = false;
+ for (auto& destination : _destinations) {
+ if (destination.hash() == packet.destination_hash()) {
+ //local_destination = destination;
+ found_local = true;
+ break;
+ }
+ }
+ //if local_destination != None:
+ //if (local_destination) {
+ if (found_local) {
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(packet.destination_hash());
+ //if (iter != _destinations.end()) {
+ // local_destination = (*iter).second;
+ //}
+ if (iter != _destinations.end()) {
+#endif
+ TRACE("Allowing announce broadcast on roaming-mode interface from instance-local destination");
+ }
+ else {
+ const Interface& from_interface = next_hop_interface(packet.destination_hash());
+ //if from_interface == None or not hasattr(from_interface, "mode"):
+ if (!from_interface || from_interface.mode() == Type::Interface::MODE_NONE) {
+ should_transmit = false;
+ if (!from_interface) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist");
+ }
+ else if (from_interface.mode() == Type::Interface::MODE_NONE) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured");
+ }
+ }
+ else {
+ if (from_interface.mode() == Type::Interface::MODE_ROAMING) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface");
+ should_transmit = false;
+ }
+ else if (from_interface.mode() == Type::Interface::MODE_BOUNDARY) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " due to boundary-mode next-hop interface");
+ should_transmit = false;
+ }
+ }
+ }
+ }
+ else if (interface.mode() == Type::Interface::MODE_BOUNDARY) {
+ //local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
+ // next and filter pattern?
+ // next(iterable, default)
+ // list comprehension: [x for x in xyz if x in a]
+ // CBA TODO confirm that above pattern just selects the first matching destination
+#if defined(DESTINATIONS_SET)
+ //Destination local_destination({Type::Destination::NONE});
+ bool found_local = false;
+ for (auto& destination : _destinations) {
+ if (destination.hash() == packet.destination_hash()) {
+ //local_destination = destination;
+ found_local = true;
+ break;
+ }
+ }
+ //if local_destination != None:
+ //if (local_destination) {
+ if (found_local) {
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(packet.destination_hash());
+ if (iter != _destinations.end()) {
+#endif
+ TRACE("Allowing announce broadcast on boundary-mode interface from instance-local destination");
+ }
+ else {
+ const Interface& from_interface = next_hop_interface(packet.destination_hash());
+ if (!from_interface || from_interface.mode() == Type::Interface::MODE_NONE) {
+ should_transmit = false;
+ if (!from_interface) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface doesn't exist");
+ }
+ else if (from_interface.mode() == Type::Interface::MODE_NONE) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " since next hop interface has no mode configured");
+ }
+ }
+ else {
+ if (from_interface.mode() == Type::Interface::MODE_ROAMING) {
+ TRACE("Blocking announce broadcast on " + interface.toString() + " due to roaming-mode next-hop interface");
+ should_transmit = false;
+ }
+ }
+ }
+ }
+ else {
+ // Currently, annouces originating locally are always
+ // allowed, and do not conform to bandwidth caps.
+ // TODO: Rethink whether this is actually optimal.
+ if (packet.hops() > 0) {
+
+// TODO
+/*p
+ if not hasattr(interface, "announce_cap"):
+ interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
+
+ if not hasattr(interface, "announce_allowed_at"):
+ interface.announce_allowed_at = 0
+
+ if not hasattr(interface, "announce_queue"):
+ interface.announce_queue = []
+*/
+
+ bool queued_announces = (interface.announce_queue().size() > 0);
+ if (!queued_announces && outbound_time > interface.announce_allowed_at()) {
+ uint16_t wait_time = 0;
+ if (interface.bitrate() > 0 && interface.announce_cap() > 0) {
+ uint16_t tx_time = (packet.raw().size() * 8) / interface.bitrate();
+ wait_time = (tx_time / interface.announce_cap());
+ }
+#if defined(INTERFACES_SET)
+ const_cast<Interface&>(interface).announce_allowed_at(outbound_time + wait_time);
+#else
+ interface.announce_allowed_at(outbound_time + wait_time);
+#endif
+ }
+ else {
+ should_transmit = false;
+ if (interface.announce_queue().size() < Type::Reticulum::MAX_QUEUED_ANNOUNCES) {
+ bool should_queue = true;
+ for (auto& entry : interface.announce_queue()) {
+ if (entry._destination == packet.destination_hash()) {
+ uint64_t emission_timestamp = announce_emitted(packet);
+ should_queue = false;
+ if (emission_timestamp > entry._emitted) {
+ entry._time = outbound_time;
+ entry._hops = packet.hops();
+ entry._emitted = emission_timestamp;
+ entry._raw = packet.raw();
+ }
+ break;
+ }
+ }
+ if (should_queue) {
+ RNS::AnnounceEntry entry(
+ packet.destination_hash(),
+ outbound_time,
+ packet.hops(),
+ announce_emitted(packet),
+ packet.raw()
+ );
+
+ queued_announces = (interface.announce_queue().size() > 0);
+#if defined(INTERFACES_SET)
+ const_cast<Interface&>(interface).add_announce(entry);
+#else
+ // CBA ACCUMULATES
+ interface.add_announce(entry);
+#endif
+
+ if (!queued_announces) {
+ double wait_time = std::max(interface.announce_allowed_at() - OS::time(), (double)0);
+
+ // CBA TODO THREAD?
+ //z timer = threading.Timer(wait_time, interface.process_announce_queue)
+ //z timer.start()
+
+ if (wait_time < 1000) {
+ TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string((int)wait_time) + " ms");
+ }
+ else {
+ TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string(OS::round(wait_time/1000,1)) + " s");
+ }
+ }
+ else {
+ double wait_time = std::max(interface.announce_allowed_at() - OS::time(), (double)0);
+ if (wait_time < 1000) {
+ TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string((int)wait_time) + " ms");
+ }
+ else {
+ TRACE("Added announce to queue (height " + std::to_string(interface.announce_queue().size()) + ") on " + interface.toString() + " for processing in " + std::to_string(OS::round(wait_time/1000,1)) + " s");
+ }
+ }
+ }
+ }
+ else {
+ //p pass
+ }
+ }
+ }
+ else {
+ //p pass
+ }
+ }
+ }
+ }
+
+ if (should_transmit) {
+ TRACE("Transport::outbound: Packet transmission allowed");
+ if (!stored_hash) {
+ // CBA ACCUMULATES
+ _packet_hashlist.insert(packet.packet_hash());
+ stored_hash = true;
+ }
+
+ // TODO: Re-evaluate potential for blocking
+ // def send_packet():
+ // Transport.transmit(interface, packet.raw)
+ // thread = threading.Thread(target=send_packet)
+ // thread.daemon = True
+ // thread.start()
+
+#if defined(INTERFACES_SET)
+ transmit(const_cast<Interface&>(interface), packet.raw());
+#else
+ transmit(interface, packet.raw());
+#endif
+ sent = true;
+ }
+ else {
+ TRACE("Transport::outbound: Packet transmission refused");
+ }
+ }
+ }
+ }
+
+ if (sent) {
+ packet.sent(true);
+ packet.sent_at(OS::time());
+
+ // Don't generate receipt if it has been explicitly disabled
+ if (packet.create_receipt() &&
+ // Only generate receipts for DATA packets
+ packet.packet_type() == Type::Packet::DATA &&
+ // Don't generate receipts for PLAIN destinations
+ packet.destination().type() != Type::Destination::PLAIN &&
+ // Don't generate receipts for link-related packets
+ !(packet.context() >= Type::Packet::KEEPALIVE && packet.context() <= Type::Packet::LRPROOF) &&
+ // Don't generate receipts for resource packets
+ !(packet.context() >= Type::Packet::RESOURCE && packet.context() <= Type::Packet::RESOURCE_RCL)) {
+
+ PacketReceipt receipt(packet);
+ packet.receipt(receipt);
+ // CBA ACCUMULATES
+ _receipts.push_back(receipt);
+ }
+
+ cache_packet(packet);
+ }
+
+ _jobs_locked = false;
+ return sent;
+}
+
+/*static*/ bool Transport::packet_filter(const Packet& packet) {
+ // TODO: Think long and hard about this.
+ // Is it even strictly necessary with the current
+ // transport rules?
+ if (packet.context() == Type::Packet::KEEPALIVE) {
+ return true;
+ }
+ if (packet.context() == Type::Packet::RESOURCE_REQ) {
+ return true;
+ }
+ if (packet.context() == Type::Packet::RESOURCE_PRF) {
+ return true;
+ }
+ if (packet.context() == Type::Packet::RESOURCE) {
+ return true;
+ }
+ if (packet.context() == Type::Packet::CACHE_REQUEST) {
+ return true;
+ }
+ if (packet.context() == Type::Packet::CHANNEL) {
+ return true;
+ }
+
+ if (packet.destination_type() == Type::Destination::PLAIN) {
+ if (packet.packet_type() != Type::Packet::ANNOUNCE) {
+ if (packet.hops() > 1) {
+ DEBUG("Dropped PLAIN packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops");
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+ else {
+ DEBUG("Dropped invalid PLAIN announce packet");
+ return false;
+ }
+ }
+
+ if (packet.destination_type() == Type::Destination::GROUP) {
+ if (packet.packet_type() != Type::Packet::ANNOUNCE) {
+ if (packet.hops() > 1) {
+ DEBUG("Dropped GROUP packet " + packet.packet_hash().toHex() + " with " + std::to_string(packet.hops()) + " hops");
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+ else {
+ DEBUG("Dropped invalid GROUP announce packet");
+ return false;
+ }
+ }
+
+ if (_packet_hashlist.find(packet.packet_hash()) == _packet_hashlist.end()) {
+ TRACE("Transport::packet_filter: packet not previously seen");
+ return true;
+ }
+ else {
+ if (packet.packet_type() == Type::Packet::ANNOUNCE) {
+ if (packet.destination_type() == Type::Destination::SINGLE) {
+ TRACE("Transport::packet_filter: packet previously seen but is SINGLE ANNOUNCE");
+ return true;
+ }
+ else {
+ DEBUG("Dropped invalid announce packet");
+ return false;
+ }
+ }
+ }
+
+ DEBUG("Filtered packet with hash " + packet.packet_hash().toHex());
+ return false;
+}
+
+/*static*/ void Transport::inbound(const Bytes& raw, const Interface& interface /*= {Type::NONE}*/) {
+ TRACEF("Transport::inbound: received %d bytes", raw.size());
+ ++_packets_received;
+ // CBA
+ if (_callbacks._receive_packet) {
+ try {
+ _callbacks._receive_packet(raw, interface);
+ }
+ catch (std::exception& e) {
+ DEBUG("Error while executing receive packet callback. The contained exception was: " + std::string(e.what()));
+ }
+ }
+// TODO
+/*p
+ // If interface access codes are enabled,
+ // we must authenticate each packet.
+ //if len(raw) > 2:
+ if (raw.size() > 2) {
+ if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
+ // Check that IFAC flag is set
+ if raw[0] & 0x80 == 0x80:
+ if len(raw) > 2+interface.ifac_size:
+ // Extract IFAC
+ ifac = raw[2:2+interface.ifac_size]
+
+ // Generate mask
+ mask = RNS.Cryptography.hkdf(
+ length=len(raw),
+ derive_from=ifac,
+ salt=interface.ifac_key,
+ context=None,
+ )
+
+ // Unmask payload
+ i = 0; unmasked_raw = b""
+ for byte in raw:
+ if i <= 1 or i > interface.ifac_size+1:
+ // Unmask header bytes and payload
+ unmasked_raw += bytes([byte ^ mask[i]])
+ else:
+ // Don't unmask IFAC itself
+ unmasked_raw += bytes([byte])
+ i += 1
+ raw = unmasked_raw
+
+ // Unset IFAC flag
+ new_header = bytes([raw[0] & 0x7f, raw[1]])
+
+ // Re-assemble packet
+ new_raw = new_header+raw[2+interface.ifac_size:]
+
+ // Calculate expected IFAC
+ expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:]
+
+ // Check it
+ if ifac == expected_ifac:
+ raw = new_raw
+ else:
+ return
+
+ else:
+ return
+
+ else:
+ // If the IFAC flag is not set, but should be,
+ // drop the packet.
+ return
+
+ else:
+ // If the interface does not have IFAC enabled,
+ // check the received packet IFAC flag.
+ if raw[0] & 0x80 == 0x80:
+ // If the flag is set, drop the packet
+ return
+ }
+ else {
+ return;
+ }
+*/
+
+ while (_jobs_running) {
+ TRACE("Transport::inbound: sleeping...");
+ OS::sleep(0.0005);
+ }
+
+ if (!_identity) {
+ WARNING("Transport::inbound: No identity!");
+ return;
+ }
+
+ _jobs_locked = true;
+
+ Packet packet(RNS::Destination(RNS::Type::NONE), raw);
+ if (!packet.unpack()) {
+ WARNING("Transport::inbound: Packet unpack failed!");
+ return;
+ }
+#ifndef NDEBUG
+ TRACE("Transport::inbound: packet: " + packet.debugString());
+#endif
+
+ TRACE("Transport::inbound: destination=" + packet.destination_hash().toHex() + " hops=" + std::to_string(packet.hops()));
+
+ packet.receiving_interface(interface);
+ packet.hops(packet.hops() + 1);
+
+// TODO
+/*p
+ if (interface) {
+ if hasattr(interface, "r_stat_rssi"):
+ if interface.r_stat_rssi != None:
+ packet.rssi = interface.r_stat_rssi
+ if len(Transport.local_client_interfaces) > 0:
+ Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
+
+ while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
+ Transport.local_client_rssi_cache.pop()
+
+ if hasattr(interface, "r_stat_snr"):
+ if interface.r_stat_rssi != None:
+ packet.snr = interface.r_stat_snr
+ if len(Transport.local_client_interfaces) > 0:
+ Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
+
+ while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
+ Transport.local_client_snr_cache.pop()
+ }
+*/
+
+ if (_local_client_interfaces.size() > 0) {
+ if (is_local_client_interface(interface)) {
+ packet.hops(packet.hops() - 1);
+ }
+ }
+ else if (interface_to_shared_instance(interface)) {
+ packet.hops(packet.hops() - 1);
+ }
+
+ //if (packet_filter(packet)) {
+ // CBA
+ bool accept = true;
+ if (_callbacks._filter_packet) {
+ try {
+ accept = _callbacks._filter_packet(packet);
+ }
+ catch (std::exception& e) {
+ DEBUG("Error while executing filter packet callback. The contained exception was: " + std::string(e.what()));
+ }
+ }
+ if (accept) {
+ accept = packet_filter(packet);
+ }
+ if (accept) {
+ TRACE("Transport::inbound: Packet accepted by filter");
+
+ // BOUNDARY MODE: Gate backbone traffic using two whitelists.
+ // Whitelist 1: local device addresses (LoRa + LocalTCP)
+ // Whitelist 2: addresses mentioned in packets from local devices
+#ifdef BOUNDARY_MODE
+ {
+ bool is_backbone = is_backbone_interface(packet.receiving_interface());
+ if (is_backbone) {
+ bool allowed = false;
+ // Whitelist 1: destination is a local device
+ if (_boundary_local_addresses.find(packet.destination_hash()) != _boundary_local_addresses.end()) {
+ allowed = true;
+ }
+ // Whitelist 2: destination was mentioned by a local device
+ else if (_boundary_mentioned_addresses.find(packet.destination_hash()) != _boundary_mentioned_addresses.end()) {
+ allowed = true;
+ }
+ // Allow return traffic: proofs routed via reverse_table
+ // (destination is the packet hash of a packet we forwarded)
+ else if (_reverse_table.find(packet.destination_hash()) != _reverse_table.end()) {
+ allowed = true;
+ }
+ // Allow return traffic: link proofs and link data routed via link_table
+ // (destination is the link_id of a link we're transporting)
+ else if (_link_table.find(packet.destination_hash()) != _link_table.end()) {
+ allowed = true;
+ }
+ // Allow packets addressed to our own control destinations
+ // (e.g. path request handler) so backbone nodes can discover
+ // paths to local devices through us
+ else if (_control_hashes.find(packet.destination_hash()) != _control_hashes.end()) {
+ allowed = true;
+ }
+ // Allow packets addressed to our own registered destinations
+ else if (_destinations.find(packet.destination_hash()) != _destinations.end()) {
+ allowed = true;
+ }
+ if (!allowed) {
+ return;
+ }
+ }
+ else {
+ // Packet from local interface: add its destination to Whitelist 2
+ _boundary_mentioned_addresses.insert(packet.destination_hash());
+ }
+ }
+#endif
+
+ // CBA ACCUMULATES
+ _packet_hashlist.insert(packet.packet_hash());
+ cache_packet(packet);
+
+ // Check special conditions for local clients connected
+ // through a shared Reticulum instance
+ //p from_local_client = (packet.receiving_interface in Transport.local_client_interfaces)
+ bool from_local_client = (_local_client_interfaces.find(packet.receiving_interface()) != _local_client_interfaces.end());
+ //p for_local_client = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.destination_table and Transport.destination_table[packet.destination_hash][2] == 0)
+ //p for_local_client_link = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][4] in Transport.local_client_interfaces)
+ //p for_local_client_link |= (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][2] in Transport.local_client_interfaces)
+
+ // If packet is anything besides ANNOUNCE then determine if it's destinated for a local destination or link
+ bool for_local_client = false;
+ bool for_local_client_link = false;
+ if (packet.packet_type() != Type::Packet::ANNOUNCE) {
+ auto destination_iter = _destination_table.find(packet.destination_hash());
+ if (destination_iter != _destination_table.end()) {
+ DestinationEntry destination_entry = (*destination_iter).second;
+ if (destination_entry._hops == 0) {
+ // Destined for a local destination
+ for_local_client = true;
+ }
+ }
+ auto link_iter = _link_table.find(packet.destination_hash());
+ if (link_iter != _link_table.end()) {
+ LinkEntry link_entry = (*link_iter).second;
+ if (_local_client_interfaces.find(link_entry._receiving_interface) != _local_client_interfaces.end()) {
+ // Destined for a local link
+ for_local_client_link = true;
+ }
+ if (_local_client_interfaces.find(link_entry._outbound_interface) != _local_client_interfaces.end()) {
+ // Destined for a local link
+ for_local_client_link = true;
+ }
+ }
+ }
+
+ // Determine if packet is proof for local destination???
+ //p proof_for_local_client = (packet.destination_hash in Transport.reverse_table) and (Transport.reverse_table[packet.destination_hash][0] in Transport.local_client_interfaces)
+ bool proof_for_local_client = false;
+ auto reverse_iter = _reverse_table.find(packet.destination_hash());
+ if (reverse_iter != _reverse_table.end()) {
+ ReverseEntry reverse_entry = (*reverse_iter).second;
+ if (_local_client_interfaces.find(reverse_entry._receiving_interface) != _local_client_interfaces.end()) {
+ // Proof for local destination???
+ proof_for_local_client = true;
+ }
+ }
+
+ // Plain broadcast packets from local clients are sent
+ // directly on all attached interfaces, since they are
+ // never injected into transport.
+
+ // If packet is not destined for a local transport-specific destination
+ if (_control_hashes.find(packet.destination_hash()) == _control_hashes.end()) {
+ // If packet is destination type PLAIN and transport type BROADCAST
+ if (packet.destination_type() == Type::Destination::PLAIN && packet.transport_type() == Type::Transport::BROADCAST) {
+ // Send to all interfaces except the one the packet was recieved on
+ if (from_local_client) {
+#if defined(INTERFACES_SET)
+ for (const Interface& interface : _interfaces) {
+#elif defined(INTERFACES_LIST)
+ for (Interface& interface : _interfaces) {
+#elif defined(INTERFACES_MAP)
+ for (auto& [hash, interface] : _interfaces) {
+#endif
+ if (interface != packet.receiving_interface()) {
+ TRACE("Transport::inbound: Broadcasting packet on " + interface.toString());
+#if defined(INTERFACES_SET)
+ transmit(const_cast<Interface&>(interface), packet.raw());
+#else
+ transmit(interface, packet.raw());
+#endif
+ }
+ }
+ }
+ // If the packet was not from a local client, send
+ // it directly to all local clients
+ else {
+ for (const Interface& interface : _local_client_interfaces) {
+ TRACE("Transport::inbound: Broadcasting packet on " + interface.toString());
+ transmit(const_cast<Interface&>(interface), packet.raw());
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////
+ // TRANSPORT HANDLING
+ ////////////////////////////////
+
+ // General transport handling. Takes care of directing
+ // packets according to transport tables and recording
+ // entries in reverse and link tables.
+ if (Reticulum::transport_enabled() || from_local_client || for_local_client || for_local_client_link) {
+ TRACE("Transport::inbound: Performing general transport handling");
+
+ // If there is no transport id, but the packet is
+ // for a local client, we generate the transport
+ // id (it was stripped on the previous hop, since
+ // we "spoof" the hop count for clients behind a
+ // shared instance, so they look directly reach-
+ // able), and reinsert, so the normal transport
+ // implementation can handle the packet.
+ if (!packet.transport_id() && for_local_client) {
+ TRACE("Transport::inbound: Regenerating transport id");
+ packet.transport_id(_identity.hash());
+ }
+
+ // If this is a cache request, and we can fullfill
+ // it, do so and stop processing. Otherwise resume
+ // normal processing.
+ if (packet.context() == Type::Packet::CACHE_REQUEST) {
+ if (cache_request_packet(packet)) {
+ TRACE("Transport::inbound: Cached packet");
+ return;
+ }
+ }
+
+ // If the packet is in transport, check whether we
+ // are the designated next hop, and process it
+ // accordingly if we are.
+ if (packet.transport_id() && packet.packet_type() != Type::Packet::ANNOUNCE) {
+ TRACE("Transport::inbound: Packet is in transport...");
+ if (packet.transport_id() == _identity.hash()) {
+ TRACE("Transport::inbound: We are designated next-hop");
+ auto destination_iter = _destination_table.find(packet.destination_hash());
+ if (destination_iter != _destination_table.end()) {
+ TRACE("Transport::inbound: Found next-hop path to destination");
+ DestinationEntry destination_entry = (*destination_iter).second;
+ Bytes next_hop = destination_entry._received_from;
+ uint8_t remaining_hops = destination_entry._hops;
+
+ // CBA RESERVE
+ //Bytes new_raw;
+ Bytes new_raw(512);
+ if (remaining_hops > 1) {
+ // Just increase hop count and transmit
+ //new_raw = packet.raw[0:1]
+ new_raw << packet.raw().left(1);
+ //new_raw += struct.pack("!B", packet.hops)
+ new_raw << packet.hops();
+ //new_raw += next_hop
+ new_raw << next_hop;
+ //new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
+ new_raw << packet.raw().mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2);
+ }
+ else if (remaining_hops == 1) {
+ // Strip transport headers and transmit
+ //new_flags = (RNS.Packet.HEADER_1) << 6 | (Transport.BROADCAST) << 4 | (packet.flags & 0b00001111)
+ uint8_t new_flags = (Type::Packet::HEADER_1) << 6 | (Type::Transport::BROADCAST) << 4 | (packet.flags() & 0b00001111);
+ //new_raw = struct.pack("!B", new_flags)
+ new_raw << new_flags;
+ //new_raw += struct.pack("!B", packet.hops)
+ new_raw << packet.hops();
+ //new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
+ new_raw << packet.raw().mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2);
+ }
+ else if (remaining_hops == 0) {
+ // Just increase hop count and transmit
+ //new_raw = packet.raw[0:1]
+ new_raw << packet.raw().left(1);
+ //new_raw += struct.pack("!B", packet.hops)
+ new_raw << packet.hops();
+ //new_raw += packet.raw[2:]
+ new_raw << packet.raw().mid(2);
+ }
+
+ Interface outbound_interface = destination_entry.receiving_interface();
+
+ if (packet.packet_type() == Type::Packet::LINKREQUEST) {
+ TRACE("Transport::inbound: Packet is next-hop LINKREQUEST");
+ double now = OS::time();
+ double proof_timeout = now + Type::Link::ESTABLISHMENT_TIMEOUT_PER_HOP * std::max((uint8_t)1, remaining_hops);
+ LinkEntry link_entry(
+ now,
+ next_hop,
+ outbound_interface,
+ remaining_hops,
+ packet.receiving_interface(),
+ packet.hops(),
+ packet.destination_hash(),
+ false,
+ proof_timeout
+ );
+ // CBA ACCUMULATES
+ _link_table.insert({Link::link_id_from_lr_packet(packet), link_entry});
+ }
+ else {
+ TRACE("Transport::inbound: Packet is next-hop other type");
+ ReverseEntry reverse_entry(
+ packet.receiving_interface(),
+ outbound_interface,
+ OS::time()
+ );
+ // CBA ACCUMULATES
+ _reverse_table.insert({packet.getTruncatedHash(), reverse_entry});
+ }
+ TRACE("Transport::outbound: Sending packet to next hop...");
+#if defined(INTERFACES_SET)
+ transmit(const_cast<Interface&>(outbound_interface), new_raw);
+#else
+ transmit(outbound_interface, new_raw);
+#endif
+ destination_entry._timestamp = OS::time();
+ }
+ else {
+#ifdef BOUNDARY_MODE
+ // BOUNDARY MODE: No path to destination. If packet came from
+ // a local device (non-backbone), request the path.
+ {
+ bool from_backbone = is_backbone_interface(packet.receiving_interface());
+ if (!from_backbone) {
+ DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local device packet. Requesting path.");
+ request_path(packet.destination_hash());
+ }
+ }
+#else
+ // TODO: There should probably be some kind of REJECT
+ // mechanism here, to signal to the source that their
+ // expected path failed.
+ TRACE("Got packet in transport, but no known path to final destination " + packet.destination_hash().toHex() + ". Dropping packet.");
+#endif
+ }
+ }
+ else {
+ TRACE("Transport::inbound: We are not designated next-hop so not transporting");
+ }
+ }
+ else {
+ TRACE("Transport::inbound: Either packet is announce or packet has no next-hop (possibly for a local destination)");
+#ifdef BOUNDARY_MODE
+ // BOUNDARY MODE: If this packet came from a local interface and we
+ // have a path to the destination, wrap it with transport headers
+ // and forward it through the backbone as the first transport hop.
+ // Skip ANNOUNCE and PROOF packets — announces have their own handling,
+ // and link proofs (LRPROOF) are handled by the LRPROOF transport code.
+ // Also skip packets destined for locally-registered destinations
+ // (e.g. path request handler) — those must be processed locally.
+ bool is_local_destination = false;
+#if defined(DESTINATIONS_MAP)
+ is_local_destination = (_destinations.find(packet.destination_hash()) != _destinations.end());
+#elif defined(DESTINATIONS_SET)
+ for (auto& dest : _destinations) {
+ if (dest.hash() == packet.destination_hash()) { is_local_destination = true; break; }
+ }
+#endif
+ if (!is_local_destination && packet.packet_type() != Type::Packet::ANNOUNCE && packet.packet_type() != Type::Packet::PROOF) {
+ bool is_from_backbone = is_backbone_interface(packet.receiving_interface());
+ if (!is_from_backbone) {
+ auto destination_iter = _destination_table.find(packet.destination_hash());
+ if (destination_iter != _destination_table.end()) {
+ DestinationEntry& dest_entry = (*destination_iter).second;
+ Bytes next_hop = dest_entry._received_from;
+ uint8_t remaining_hops = dest_entry._hops;
+ Interface outbound_interface = dest_entry.receiving_interface();
+
+ // Build outgoing packet based on remaining hops,
+ // mirroring standard transport forwarding logic.
+ Bytes new_raw(512);
+ if (remaining_hops > 1) {
+ // Multi-hop: wrap with HEADER_2/TRANSPORT,
+ // setting transport_id = next_hop (the next
+ // transport node's identity hash from announce).
+ uint8_t new_flags = (Type::Packet::HEADER_2) << 6
+ | (Type::Transport::TRANSPORT) << 4
+ | (packet.flags() & 0b00001111);
+ new_raw << new_flags;
+ new_raw << packet.hops();
+ new_raw << next_hop; // insert transport_id
+ new_raw << packet.raw().mid(2); // destination_hash + payload
+ }
+ else {
+ // Single hop (remaining_hops <= 1): destination is
+ // directly reachable. Send as HEADER_1/BROADCAST
+ // (no transport header), matching standard transport
+ // behaviour for final-hop delivery.
+ uint8_t new_flags = (Type::Packet::HEADER_1) << 6
+ | (Type::Transport::BROADCAST) << 4
+ | (packet.flags() & 0b00001111);
+ new_raw << new_flags;
+ new_raw << packet.hops();
+ new_raw << packet.raw().mid(2); // destination_hash + payload
+ }
+
+ // Create link_table or reverse_table entry for return path
+ if (packet.packet_type() == Type::Packet::LINKREQUEST) {
+ double now = OS::time();
+ double proof_timeout = now + Type::Link::ESTABLISHMENT_TIMEOUT_PER_HOP
+ * std::max((uint8_t)1, remaining_hops);
+ LinkEntry link_entry(
+ now, next_hop, outbound_interface, remaining_hops,
+ packet.receiving_interface(), packet.hops(),
+ packet.destination_hash(), false, proof_timeout
+ );
+ // Each LINKREQUEST gets its own entry (unique link_id)
+ _link_table.insert({Link::link_id_from_lr_packet(packet), link_entry});
+ }
+ else {
+ ReverseEntry reverse_entry(
+ packet.receiving_interface(), outbound_interface, OS::time()
+ );
+ _reverse_table.insert({packet.getTruncatedHash(), reverse_entry});
+ }
+
+ DEBUG("BOUNDARY: Forwarding local packet (" + std::to_string(remaining_hops) + " hops, " + std::to_string(new_raw.size()) + " bytes) to " + outbound_interface.toString() + " for " + packet.destination_hash().toHex());
+ transmit(outbound_interface, new_raw);
+ dest_entry._timestamp = OS::time();
+ }
+ else {
+ DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local packet. Requesting path.");
+ request_path(packet.destination_hash());
+ }
+ }
+ else {
+ // BOUNDARY MODE REVERSE: Packet came from backbone,
+ // check if destination is a local LoRa device and forward it.
+ if (_boundary_local_addresses.find(packet.destination_hash()) != _boundary_local_addresses.end()) {
+ auto destination_iter = _destination_table.find(packet.destination_hash());
+ if (destination_iter != _destination_table.end()) {
+ DestinationEntry& dest_entry = (*destination_iter).second;
+ Interface outbound_interface = dest_entry.receiving_interface();
+
+ // Create reverse_table entry so proof can get back
+ ReverseEntry reverse_entry(
+ packet.receiving_interface(), outbound_interface, OS::time()
+ );
+ _reverse_table.insert({packet.getTruncatedHash(), reverse_entry});
+
+ DEBUG("BOUNDARY: Forwarding backbone packet to local device for " + packet.destination_hash().toHex() + " via " + outbound_interface.toString());
+ transmit(outbound_interface, packet.raw());
+ dest_entry._timestamp = OS::time();
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ // Link transport handling. Directs packets according
+ // to entries in the link tables
+ if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.packet_type() != Type::Packet::LINKREQUEST && packet.context() != Type::Packet::LRPROOF) {
+ TRACE("Transport::inbound: Checking if packet is meant for link transport...");
+ auto link_iter = _link_table.find(packet.destination_hash());
+ if (link_iter != _link_table.end()) {
+ TRACE("Transport::inbound: Found link entry, handling link transport");
+ LinkEntry link_entry = (*link_iter).second;
+ // If receiving and outbound interface is
+ // the same for this link, direction doesn't
+ // matter, and we simply send the packet on.
+ Interface outbound_interface({Type::NONE});
+ if (link_entry._outbound_interface == link_entry._receiving_interface) {
+ // But check that taken hops matches one
+ // of the expectede values.
+ if (packet.hops() == link_entry._remaining_hops || packet.hops() == link_entry._hops) {
+ TRACE("Transport::inbound: Link inbound/outbound interfaes are same, transporting on same interface");
+ outbound_interface = link_entry._outbound_interface;
+ }
+ }
+ else {
+ // If interfaces differ, we transmit on
+ // the opposite interface of what the
+ // packet was received on.
+ if (packet.receiving_interface() == link_entry._outbound_interface) {
+ // Also check that expected hop count matches
+ if (packet.hops() == link_entry._remaining_hops) {
+ TRACE("Transport::inbound: Link transporting on inbound interface");
+ outbound_interface = link_entry._receiving_interface;
+ }
+ }
+ else if (packet.receiving_interface() == link_entry._receiving_interface) {
+ // Also check that expected hop count matches
+ if (packet.hops() == link_entry._hops) {
+ TRACE("Transport::inbound: Link transporting on outbound interface");
+ outbound_interface = link_entry._outbound_interface;
+ }
+ }
+ }
+
+ if (outbound_interface) {
+ TRACE("Transport::inbound: Transmitting link transport packet");
+ // CBA RESERVE
+ //Bytes new_raw;
+ Bytes new_raw(512);
+ //new_raw = packet.raw[0:1]
+ new_raw << packet.raw().left(1);
+ //new_raw += struct.pack("!B", packet.hops)
+ new_raw << packet.hops();
+ //new_raw += packet.raw[2:]
+ new_raw << packet.raw().mid(2);
+ transmit(outbound_interface, new_raw);
+ link_entry._timestamp = OS::time();
+ }
+ else {
+ //p pass
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////
+ // LOCAL HANDLING
+ ////////////////////////////////
+
+ // Announce handling. Handles logic related to incoming
+ // announces, queueing rebroadcasts of these, and removal
+ // of queued announce rebroadcasts once handed to the next node.
+ if (packet.packet_type() == Type::Packet::ANNOUNCE) {
+ TRACE("Transport::inbound: Packet is ANNOUNCE");
+ Bytes received_from;
+ //p local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
+#if defined(DESTINATIONS_SET)
+ //Destination local_destination({Type::NONE});
+ bool found_local = false;
+ for (auto& destination : _destinations) {
+ if (destination.hash() == packet.destination_hash()) {
+ //local_destination = destination;
+ found_local = true;
+ break;
+ }
+ }
+ //if local_destination == None and RNS.Identity.validate_announce(packet):
+ //if (!local_destination && Identity::validate_announce(packet)) {
+ if (!found_local && Identity::validate_announce(packet)) {
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(packet.destination_hash());
+ if (iter == _destinations.end() && Identity::validate_announce(packet)) {
+#endif
+ TRACE("Transport::inbound: Packet is announce for non-local destination, processing...");
+ if (packet.transport_id()) {
+ received_from = packet.transport_id();
+
+ // Check if this is a next retransmission from
+ // another node. If it is, we're removing the
+ // announce in question from our pending table
+ if (Reticulum::transport_enabled() && _announce_table.count(packet.destination_hash()) > 0) {
+ //AnnounceEntry& announce_entry = _announce_table[packet.destination_hash()];
+ AnnounceEntry& announce_entry = (*_announce_table.find(packet.destination_hash())).second;
+
+ if ((packet.hops() - 1) == announce_entry._hops) {
+ DEBUG("Heard a local rebroadcast of announce for " + packet.destination_hash().toHex());
+ announce_entry._local_rebroadcasts += 1;
+ if (announce_entry._local_rebroadcasts >= LOCAL_REBROADCASTS_MAX) {
+ DEBUG("Max local rebroadcasts of announce for " + packet.destination_hash().toHex() + " reached, dropping announce from our table");
+ _announce_table.erase(packet.destination_hash());
+ }
+ }
+
+ if ((packet.hops() - 1) == (announce_entry._hops + 1) && announce_entry._retries > 0) {
+ double now = OS::time();
+ if (now < announce_entry._timestamp) {
+ DEBUG("Rebroadcasted announce for " + packet.destination_hash().toHex() + " has been passed on to another node, no further tries needed");
+ _announce_table.erase(packet.destination_hash());
+ }
+ }
+ }
+ }
+ else {
+ received_from = packet.destination_hash();
+ }
+
+ // Check if this announce should be inserted into
+ // announce and destination tables
+ bool should_add = false;
+
+ // First, check that the announce is not for a destination
+ // local to this system, and that hops are less than the max
+ // CBA TODO determine why packet destination hash is being searched in destinations again since we entered this logic becuase it did not exist above
+ //if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1):
+#if defined(DESTINATIONS_SET)
+ bool found_local = false;
+ for (auto& destination : _destinations) {
+ if (destination.hash() == packet.destination_hash()) {
+ found_local = true;
+ break;
+ }
+ }
+ if (!found_local && packet.hops() < (PATHFINDER_M+1)) {
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(packet.destination_hash());
+ if (iter == _destinations.end() && packet.hops() < (PATHFINDER_M+1)) {
+#endif
+ uint64_t announce_emitted = Transport::announce_emitted(packet);
+
+ //p random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
+ Bytes random_blob = packet.data().mid(Type::Identity::KEYSIZE/8 + Type::Identity::NAME_HASH_LENGTH/8, Type::Identity::RANDOM_HASH_LENGTH/8);
+ //p random_blobs = []
+ std::set<Bytes> empty_random_blobs;
+ std::set<Bytes>& random_blobs = empty_random_blobs;
+ auto iter = _destination_table.find(packet.destination_hash());
+ if (iter != _destination_table.end()) {
+ DestinationEntry destination_entry = (*iter).second;
+ //p random_blobs = Transport.destination_table[packet.destination_hash][4]
+ random_blobs = destination_entry._random_blobs;
+
+ // If we already have a path to the announced
+ // destination, but the hop count is equal or
+ // less, we'll update our tables.
+ if (packet.hops() <= destination_entry._hops) {
+ // Make sure we haven't heard the random
+ // blob before, so announces can't be
+ // replayed to forge paths.
+ // TODO: Check whether this approach works
+ // under all circumstances
+ //p if not random_blob in random_blobs:
+ if (random_blobs.find(random_blob) == random_blobs.end()) {
+ should_add = true;
+ }
+ else {
+ should_add = false;
+ }
+ }
+ else {
+ // If an announce arrives with a larger hop
+ // count than we already have in the table,
+ // ignore it, unless the path is expired, or
+ // the emission timestamp is more recent.
+ double now = OS::time();
+ double path_expires = destination_entry._expires;
+
+ uint64_t path_announce_emitted = 0;
+ for (const Bytes& path_random_blob : random_blobs) {
+ //p path_announce_emitted = max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big"))
+ path_announce_emitted = std::max(path_announce_emitted, OS::from_bytes_big_endian(path_random_blob.data() + 5, 5));
+ if (path_announce_emitted >= announce_emitted) {
+ break;
+ }
+ }
+
+ if (now >= path_expires) {
+ // We also check that the announce is
+ // different from ones we've already heard,
+ // to avoid loops in the network
+ if (random_blobs.find(random_blob) == random_blobs.end()) {
+ // TODO: Check that this ^ approach actually
+ // works under all circumstances
+ DEBUG("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce due to expired path");
+ should_add = true;
+ }
+ else {
+ should_add = false;
+ }
+ }
+ else {
+ if (announce_emitted > path_announce_emitted) {
+ if (random_blobs.find(random_blob) == random_blobs.end()) {
+ DEBUG("Replacing destination table entry for " + packet.destination_hash().toHex() + " with new announce, since it was more recently emitted");
+ should_add = true;
+ }
+ else {
+ should_add = false;
+ }
+ }
+ }
+ }
+ }
+ else {
+ // If this destination is unknown in our table
+ // we should add it
+ should_add = true;
+ }
+
+ if (should_add) {
+ // BOUNDARY MODE: Prevent backbone echo from overwriting a local LoRa path
+#ifdef BOUNDARY_MODE
+ {
+ auto existing_iter = _destination_table.find(packet.destination_hash());
+ if (existing_iter != _destination_table.end()) {
+ DestinationEntry& existing = existing_iter->second;
+ Interface existing_iface = existing.receiving_interface();
+ bool existing_is_lora = !is_backbone_interface(existing_iface);
+ bool new_is_backbone = is_backbone_interface(packet.receiving_interface());
+ if (existing_is_lora && new_is_backbone) {
+ DEBUG("BOUNDARY: Blocking backbone announce echo from overwriting local LoRa path for " + packet.destination_hash().toHex()
+ + " (existing " + std::to_string(existing._hops) + " hops via LoRa, new " + std::to_string(packet.hops()) + " hops via backbone)");
+ should_add = false;
+ }
+ }
+ }
+#endif
+ }
+
+ if (should_add) {
+ double now = OS::time();
+
+ bool rate_blocked = false;
+
+// TODO
+/*p
+ if packet.context != RNS.Packet.PATH_RESPONSE and packet.receiving_interface.announce_rate_target != None:
+ if not packet.destination_hash in Transport.announce_rate_table:
+ rate_entry = { "last": now, "rate_violations": 0, "blocked_until": 0, "timestamps": [now]}
+ Transport.announce_rate_table[packet.destination_hash] = rate_entry
+
+ else:
+ rate_entry = Transport.announce_rate_table[packet.destination_hash]
+ rate_entry["timestamps"].append(now)
+
+ while len(rate_entry["timestamps"]) > Transport.MAX_RATE_TIMESTAMPS:
+ rate_entry["timestamps"].pop(0)
+
+ current_rate = now - rate_entry["last"]
+
+ if now > rate_entry["blocked_until"]:
+
+ if current_rate < packet.receiving_interface.announce_rate_target:
+ rate_entry["rate_violations"] += 1
+
+ else:
+ rate_entry["rate_violations"] = std::max(0, rate_entry["rate_violations"]-1)
+
+ if rate_entry["rate_violations"] > packet.receiving_interface.announce_rate_grace:
+ rate_target = packet.receiving_interface.announce_rate_target
+ rate_penalty = packet.receiving_interface.announce_rate_penalty
+ rate_entry["blocked_until"] = rate_entry["last"] + rate_target + rate_penalty
+ rate_blocked = True
+ else:
+ rate_entry["last"] = now
+
+ else:
+ rate_blocked = True
+*/
+
+ uint8_t retries = 0;
+ uint8_t announce_hops = packet.hops();
+ uint8_t local_rebroadcasts = 0;
+ bool block_rebroadcasts = false;
+ Interface attached_interface = {Type::NONE};
+
+ double retransmit_timeout = now + (Cryptography::random() * PATHFINDER_RW);
+
+ double expires;
+ if (packet.receiving_interface().mode() == Type::Interface::MODE_ACCESS_POINT) {
+ expires = now + AP_PATH_TIME;
+ }
+ else if (packet.receiving_interface().mode() == Type::Interface::MODE_ROAMING) {
+ expires = now + ROAMING_PATH_TIME;
+ }
+ else {
+ expires = now + PATHFINDER_E;
+ }
+
+ random_blobs.insert(random_blob);
+
+ if ((Reticulum::transport_enabled() || Transport::from_local_client(packet)) && packet.context() != Type::Packet::PATH_RESPONSE) {
+ // Insert announce into announce table for retransmission
+
+ if (rate_blocked) {
+ DEBUG("Blocking rebroadcast of announce from " + packet.destination_hash().toHex() + " due to excessive announce rate");
+ }
+ else {
+ if (Transport::from_local_client(packet)) {
+ // If the announce is from a local client,
+ // it is announced immediately, but only one time.
+ retransmit_timeout = now;
+ retries = PATHFINDER_R;
+ }
+ AnnounceEntry announce_entry(
+ now,
+ retransmit_timeout,
+ retries,
+ received_from,
+ announce_hops,
+ packet,
+ local_rebroadcasts,
+ block_rebroadcasts,
+ attached_interface
+ );
+ // CBA ACCUMULATES
+ _announce_table.insert({packet.destination_hash(), announce_entry});
+ }
+ }
+ // TODO: Check from_local_client once and store result
+ else if (Transport::from_local_client(packet) && packet.context() == Type::Packet::PATH_RESPONSE) {
+ // If this is a path response from a local client,
+ // check if any external interfaces have pending
+ // path requests.
+ //p if packet.destination_hash in Transport.pending_local_path_requests:
+ auto iter = _pending_local_path_requests.find(packet.destination_hash());
+ if (iter != _pending_local_path_requests.end()) {
+ //p desiring_interface = Transport.pending_local_path_requests.pop(packet.destination_hash)
+ //const Interface& desiring_interface = (*iter).second;
+ retransmit_timeout = now;
+ retries = PATHFINDER_R;
+
+ AnnounceEntry announce_entry(
+ now,
+ retransmit_timeout,
+ retries,
+ received_from,
+ announce_hops,
+ packet,
+ local_rebroadcasts,
+ block_rebroadcasts,
+ attached_interface
+ );
+ // CBA ACCUMULATES
+ _announce_table.insert({packet.destination_hash(), announce_entry});
+ }
+ }
+
+ // If we have any local clients connected, we re-
+ // transmit the announce to them immediately
+ if (_local_client_interfaces.size() > 0) {
+ Identity announce_identity(Identity::recall(packet.destination_hash()));
+ //Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown");
+ //announce_destination.hash(packet.destination_hash());
+ Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, packet.destination_hash());
+ //announce_destination.hexhash(announce_destination.hash().toHex());
+ Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
+ Bytes announce_data = packet.data();
+
+ // TODO: Shouldn't the context be PATH_RESPONSE in the first case here?
+ if (Transport::from_local_client(packet) && packet.context() == Type::Packet::PATH_RESPONSE) {
+ for (const Interface& local_interface : _local_client_interfaces) {
+ if (packet.receiving_interface() != local_interface) {
+ Packet new_announce(
+ announce_destination,
+ local_interface,
+ announce_data,
+ Type::Packet::ANNOUNCE,
+ announce_context,
+ Type::Transport::TRANSPORT,
+ Type::Packet::HEADER_2,
+ _identity.hash(),
+ true,
+ packet.context_flag()
+ );
+
+ new_announce.hops(packet.hops());
+ new_announce.send();
+ }
+ }
+ }
+ else {
+ for (const Interface& local_interface : _local_client_interfaces) {
+ if (packet.receiving_interface() != local_interface) {
+ Packet new_announce(
+ announce_destination,
+ local_interface,
+ announce_data,
+ Type::Packet::ANNOUNCE,
+ announce_context,
+ Type::Transport::TRANSPORT,
+ Type::Packet::HEADER_2,
+ _identity.hash(),
+ true,
+ packet.context_flag()
+ );
+
+ new_announce.hops(packet.hops());
+ new_announce.send();
+ }
+ }
+ }
+ }
+
+ // If we have any waiting discovery path requests
+ // for this destination, we retransmit to that
+ // interface immediately
+ auto iter = _discovery_path_requests.find(packet.destination_hash());
+ if (iter != _discovery_path_requests.end()) {
+ PathRequestEntry& pr_entry = (*iter).second;
+ attached_interface = pr_entry._requesting_interface;
+
+ DEBUG("Got matching announce, answering waiting discovery path request for " + packet.destination_hash().toHex() + " on " + attached_interface.toString());
+ Identity announce_identity(Identity::recall(packet.destination_hash()));
+ //Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, "unknown", "unknown");
+ //announce_destination.hash(packet.destination_hash());
+ Destination announce_destination(announce_identity, Type::Destination::OUT, Type::Destination::SINGLE, packet.destination_hash());
+ //announce_destination.hexhash(announce_destination.hash().toHex());
+ Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
+ Bytes announce_data = packet.data();
+
+ Packet new_announce(
+ announce_destination,
+ attached_interface,
+ announce_data,
+ Type::Packet::ANNOUNCE,
+ Type::Packet::PATH_RESPONSE,
+ Type::Transport::TRANSPORT,
+ Type::Packet::HEADER_2,
+ _identity.hash(),
+ true,
+ packet.context_flag()
+ );
+
+ new_announce.hops(packet.hops());
+ new_announce.send();
+ }
+
+ // CBA Culling before adding to esnure table does not exceed maxsize
+ TRACE("Caching packet " + packet.get_hash().toHex());
+ if (RNS::Transport::cache_packet(packet, true)) {
+ packet.cached(true);
+ }
+ //TRACE("Adding packet " + packet.get_hash().toHex() + " to packet table");
+ //PacketEntry packet_entry(packet);
+ // CBA ACCUMULATES
+ //_packet_table.insert({packet.get_hash(), packet_entry});
+ TRACE("Adding destination " + packet.destination_hash().toHex() + " to path table");
+ DestinationEntry destination_table_entry(
+ now,
+ received_from,
+ announce_hops,
+ expires,
+ random_blobs,
+ //packet.receiving_interface(),
+ //const_cast<Interface&>(packet.receiving_interface()),
+ packet.receiving_interface().get_hash(),
+ //packet
+ packet.get_hash()
+ );
+ // CBA ACCUMULATES
+ if (_destination_table.insert({packet.destination_hash(), destination_table_entry}).second) {
+ ++_destinations_added;
+ cull_path_table();
+ }
+
+ DEBUG("Destination " + packet.destination_hash().toHex() + " is now " + std::to_string(announce_hops) + " hops away via " + received_from.toHex() + " on " + packet.receiving_interface().toString());
+
+ // BOUNDARY MODE: Register destinations seen via non-backbone interfaces (Whitelist 1)
+#ifdef BOUNDARY_MODE
+ {
+ bool is_backbone = is_backbone_interface(packet.receiving_interface());
+ if (!is_backbone) {
+ _boundary_local_addresses.insert(packet.destination_hash());
+ DEBUG("BOUNDARY: Registered local address " + packet.destination_hash().toHex() + " from local interface");
+ }
+ }
+#endif
+ //TRACE("Transport::inbound: Destination " + packet.destination_hash().toHex() + " has data: " + packet.data().toHex());
+ //TRACE("Transport::inbound: Destination " + packet.destination_hash().toHex() + " has text: " + packet.data().toString());
+
+// TODO
+/*
+ // If the receiving interface is a tunnel, we add the
+ // announce to the tunnels table
+ if (packet.receiving_interface().tunnel_id()) {
+ tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id];
+ paths = tunnel_entry[2];
+ paths[packet.destination_hash] = destination_table_entry;
+ expires = OS::time() + Transport::DESTINATION_TIMEOUT;
+ tunnel_entry[3] = expires;
+ DEBUG("Path to " + packet.destination_hash().toHex() + " associated with tunnel " + packet.receiving_interface().tunnel_id().toHex());
+ }
+*/
+
+ // Call externally registered callbacks from apps
+ // wanting to know when an announce arrives
+ if (packet.context() != Type::Packet::PATH_RESPONSE) {
+ TRACE("Transport::inbound: Not path response, sending to announce handler...");
+ for (auto& handler : _announce_handlers) {
+ TRACE("Transport::inbound: Checking filter of announce handler...");
+ try {
+ // Check that the announced destination matches
+ // the handlers aspect filter
+ bool execute_callback = false;
+ Identity announce_identity(Identity::recall(packet.destination_hash()));
+ if (handler->aspect_filter().empty()) {
+ // If the handlers aspect filter is set to
+ // None, we execute the callback in all cases
+ execute_callback = true;
+ }
+ else {
+ Bytes handler_expected_hash = Destination::hash_from_name_and_identity(handler->aspect_filter().c_str(), announce_identity);
+ if (packet.destination_hash() == handler_expected_hash) {
+ execute_callback = true;
+ }
+ }
+ if (execute_callback) {
+ // CBA TODO Why does app data come from recall instead of from this announce packet?
+ handler->received_announce(
+ packet.destination_hash(),
+ announce_identity,
+ Identity::recall_app_data(packet.destination_hash())
+ );
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error while processing external announce callback.");
+ ERRORF("The contained exception was: %s", e.what());
+ }
+ }
+ }
+ }
+ }
+ else {
+ TRACE("Transport::inbound: Packet is announce for local destination, not processing");
+ }
+ }
+ else {
+ TRACE("Transport::inbound: Packet is announce for local destination, not processing");
+ }
+ }
+
+ // Handling for link requests to local destinations
+ else if (packet.packet_type() == Type::Packet::LINKREQUEST) {
+ TRACE("Transport::inbound: Packet is LINKREQUEST");
+ if (!packet.transport_id() || packet.transport_id() == _identity.hash()) {
+ TRACE("Transport::inbound: Checking if LINKREQUEST is for local destination");
+#if defined(DESTINATIONS_SET)
+ for (auto& destination : _destinations) {
+ if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) {
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(packet.destination_hash());
+ if (iter != _destinations.end()) {
+ auto& destination = (*iter).second;
+ if (destination.type() == packet.destination_type()) {
+#endif
+ TRACE("Transport::inbound: Found local destination for LINKREQUEST");
+ packet.destination(destination);
+ // CBA iterator over std::set is always const so need to make temporarily mutable
+ //destination.receive(packet);
+#if defined(DESTINATIONS_SET)
+ const_cast<Destination&>(destination).receive(packet);
+#else
+ destination.receive(packet);
+#endif
+ }
+ }
+ }
+ }
+
+ // Handling for data packets to local destinations
+ else if (packet.packet_type() == Type::Packet::DATA) {
+ TRACE("Transport::inbound: Packet is DATA");
+ if (packet.destination_type() == Type::Destination::LINK) {
+ // Data is destined for a link
+ TRACE("Transport::inbound: Packet is DATA for a LINK");
+ std::set<Link> active_links(_active_links);
+ for (auto& link : active_links) {
+ if (link.link_id() == packet.destination_hash()) {
+ TRACE("Transport::inbound: Packet is DATA for an active LINK");
+ packet.link(link);
+ const_cast<Link&>(link).receive(packet);
+ }
+ }
+ }
+ else {
+ // Data is basic (not destined for a link)
+#if defined(DESTINATIONS_SET)
+ for (auto& destination : _destinations) {
+ if (destination.hash() == packet.destination_hash() && destination.type() == packet.destination_type()) {
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(packet.destination_hash());
+ if (iter != _destinations.end()) {
+ // Data is for a local destination
+ DEBUG("Packet destination " + packet.destination_hash().toHex() + " found, destination is local");
+ auto& destination = (*iter).second;
+ if (destination.type() == packet.destination_type()) {
+ TRACE("Transport::inbound: Packet destination type " + std::to_string(packet.destination_type()) + " matched, processing");
+#endif
+ packet.destination(destination);
+#if defined(DESTINATIONS_SET)
+ const_cast<Destination&>(destination).receive(packet);
+#else
+ destination.receive(packet);
+#endif
+
+ if (destination.proof_strategy() == Type::Destination::PROVE_ALL) {
+ packet.prove();
+ }
+ else if (destination.proof_strategy() == Type::Destination::PROVE_APP) {
+ if (destination.callbacks()._proof_requested) {
+ try {
+ if (destination.callbacks()._proof_requested(packet)) {
+ packet.prove();
+ }
+ }
+ catch (std::exception& e) {
+ ERROR(std::string("Error while executing proof request callback. The contained exception was: ") + e.what());
+ }
+ }
+ }
+ }
+ else {
+ DEBUG("Transport::inbound: Packet destination type " + std::to_string(packet.destination_type()) + " mismatch, ignoring");
+ }
+ }
+ else {
+ DEBUG("Transport::inbound: Local destination " + packet.destination_hash().toHex() + " not found, not handling packet locally");
+ }
+ }
+ }
+
+ // Handling for proofs and link-request proofs
+ else if (packet.packet_type() == Type::Packet::PROOF) {
+ TRACE("Transport::inbound: Packet is PROOF");
+ if (packet.context() == Type::Packet::LRPROOF) {
+ TRACE("Transport::inbound: Packet is LINK PROOF");
+ // This is a link request proof, check if it
+ // needs to be transported
+ if ((Reticulum::transport_enabled() || for_local_client_link || from_local_client) && _link_table.find(packet.destination_hash()) != _link_table.end()) {
+ TRACE("Handling link request proof...");
+ LinkEntry link_entry = (*_link_table.find(packet.destination_hash())).second;
+ DEBUG("PROOF DEBUG: recv_iface=" + packet.receiving_interface().toString() + " out_iface=" + link_entry._outbound_interface.toString());
+
+ bool interface_match = (packet.receiving_interface() == link_entry._outbound_interface);
+ if (interface_match) {
+ try {
+ size_t expected_size = (Type::Identity::SIGLENGTH/8 + Type::Link::ECPUBSIZE/2);
+ size_t expected_size_with_mtu = expected_size + Type::Link::LINK_MTU_SIZE;
+ DEBUG("PROOF DEBUG: data_size=" + std::to_string(packet.data().size()) + " expected=" + std::to_string(expected_size) + " or " + std::to_string(expected_size_with_mtu) + " raw_size=" + std::to_string(packet.raw().size()));
+ if (packet.data().size() == expected_size || packet.data().size() == expected_size_with_mtu) {
+ Bytes signalling_bytes;
+ if (packet.data().size() == expected_size_with_mtu) {
+ signalling_bytes = Link::signalling_bytes(Link::mtu_from_lp_packet(packet), Link::mode_from_lp_packet(packet));
+ }
+
+ Bytes peer_pub_bytes = packet.data().mid(Type::Identity::SIGLENGTH/8, Type::Link::ECPUBSIZE/2);
+ Identity peer_identity = Identity::recall(link_entry._destination_hash);
+ if (!peer_identity) {
+ DEBUG("PROOF DEBUG: Cannot recall identity for " + link_entry._destination_hash.toHex() + ", dropping proof.");
+ }
+ else {
+ DEBUG("PROOF DEBUG: peer_identity recalled for " + link_entry._destination_hash.toHex() + " valid=" + std::to_string(!peer_identity.get_public_key().empty()));
+ Bytes peer_sig_pub_bytes = peer_identity.get_public_key().mid(Type::Link::ECPUBSIZE/2, Type::Link::ECPUBSIZE/2);
+
+ Bytes signed_data = packet.destination_hash() + peer_pub_bytes + peer_sig_pub_bytes + signalling_bytes;
+ Bytes signature = packet.data().left(Type::Identity::SIGLENGTH/8);
+
+ if (peer_identity.validate(signature, signed_data)) {
+ TRACE("Link request proof validated for transport via " + link_entry._receiving_interface.toString());
+ //p new_raw = packet.raw[0:1]
+ // CBA RESERVE
+ //Bytes new_raw = packet.raw().left(1);
+ Bytes new_raw(512);
+ new_raw << packet.raw().left(1);
+ //p new_raw += struct.pack("!B", packet.hops)
+ new_raw << packet.hops();
+ //p new_raw += packet.raw[2:]
+ new_raw << packet.raw().mid(2);
+ link_entry._validated = true;
+ transmit(link_entry._receiving_interface, new_raw);
+ }
+ else {
+ DEBUG("Invalid link request proof in transport for link " + packet.destination_hash().toHex() + ", dropping proof.");
+ }
+ } // end peer_identity valid
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error while transporting link request proof. The contained exception was: " + std::string(e.what()));
+ }
+ }
+ else {
+ DEBUG("PROOF DEBUG: interface mismatch - recv=" + packet.receiving_interface().toString() + " expected_out=" + link_entry._outbound_interface.toString());
+ DEBUG("Link request proof received on wrong interface, not transporting it.");
+ }
+ }
+ else {
+ // Check if we can deliver it to a local
+ // pending link
+ TRACEF("Handling proof for link request %s", packet.destination_hash().toHex().c_str());
+ // CBA Must make a copy of _pending_links before traversing since it gets modified
+ //for (auto link : _pending_links) {
+ std::set<Link> pending_links(_pending_links);
+ for (auto& link : pending_links) {
+ TRACEF("Checking for link request handling by pending link %s", link.link_id().toHex().c_str());
+ if (link.link_id() == packet.destination_hash()) {
+ TRACE("Requesting pending link to validate proof");
+ const_cast<Link&>(link).validate_proof(packet);
+ }
+ }
+ }
+ }
+ else if (packet.context() == Type::Packet::RESOURCE_PRF) {
+ TRACE("Transport::inbound: Packet is RESOURCE PROOF");
+ std::set<Link> active_links(_active_links);
+ for (auto& link : active_links) {
+ if (link.link_id() == packet.destination_hash()) {
+ const_cast<Link&>(link).receive(packet);
+ }
+ }
+ }
+ else {
+ TRACE("Transport::inbound: Packet is regular PROOF");
+ if (packet.destination_type() == Type::Destination::LINK) {
+ std::set<Link> active_links(_active_links);
+ for (auto& link : active_links) {
+ if (link.link_id() == packet.destination_hash()) {
+ packet.link(link);
+ }
+ }
+ }
+
+ Bytes proof_hash;
+ if (packet.data().size() == Type::PacketReceipt::EXPL_LENGTH) {
+ proof_hash = packet.data().left(Type::Identity::HASHLENGTH/8);
+ }
+
+ // Check if this proof needs to be transported
+ if ((Reticulum::transport_enabled() || from_local_client || proof_for_local_client) && _reverse_table.find(packet.destination_hash()) != _reverse_table.end()) {
+ ReverseEntry reverse_entry = (*_reverse_table.find(packet.destination_hash())).second;
+ if (packet.receiving_interface() == reverse_entry._outbound_interface) {
+ TRACE("Proof received on correct interface, transporting it via " + reverse_entry._receiving_interface.toString());
+ //p new_raw = packet.raw[0:1]
+ // CBA RESERVE
+ //Bytes new_raw = packet.raw().left(1);
+ Bytes new_raw(512);
+ new_raw << packet.raw().left(1);
+ //p new_raw += struct.pack("!B", packet.hops)
+ new_raw << packet.hops();
+ //p new_raw += packet.raw[2:]
+ new_raw << packet.raw().mid(2);
+ transmit(reverse_entry._receiving_interface, new_raw);
+ }
+ else {
+ DEBUG("Proof received on wrong interface, not transporting it.");
+ }
+ }
+ else {
+ TRACE("Proof is not candidate for transporting");
+ }
+
+ std::list<PacketReceipt> cull_receipts;
+ for (auto& receipt : _receipts) {
+ bool receipt_validated = false;
+ if (proof_hash) {
+ // Only test validation if hash matches
+ if (receipt.hash() == proof_hash) {
+ receipt_validated = receipt.validate_proof_packet(packet);
+ }
+ }
+ else {
+ // TODO: This looks like it should actually
+ // be rewritten when implicit proofs are added.
+
+ // In case of an implicit proof, we have
+ // to check every single outstanding receipt
+ receipt_validated = receipt.validate_proof_packet(packet);
+ }
+
+ // CBA TODO requires modifying of collection while iterating which is forbidden
+ if (receipt_validated) {
+ //p if receipt in Transport.receipts:
+ //p Transport.receipts.remove(receipt)
+ cull_receipts.push_back(receipt);
+ }
+ }
+ // CBA since modifying of collection while iterating is forbidden
+ for (auto& receipt : _receipts) {
+ cull_receipts.remove(receipt);
+ }
+ }
+ }
+ }
+
+ _jobs_locked = false;
+}
+
+/*static*/ void Transport::synthesize_tunnel(const Interface& interface) {
+// TODO
+/*p
+ Bytes interface_hash = interface.get_hash();
+ Bytes public_key = _identity.get_public_key();
+ Bytes random_hash = Identity::get_random_hash();
+
+ tunnel_id_data = public_key+interface_hash
+ tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
+
+ signed_data = tunnel_id_data+random_hash
+ signature = Transport.identity.sign(signed_data)
+
+ data = signed_data+signature
+
+ tnl_snth_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
+
+ packet = RNS.Packet(tnl_snth_dst, data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = interface)
+ packet.send()
+
+ interface.wants_tunnel = False
+*/
+}
+
+/*static*/ void Transport::tunnel_synthesize_handler(const Bytes& data, const Packet& packet) {
+// TODO
+/*p
+ try:
+ expected_length = RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8+RNS.Identity.SIGLENGTH//8
+ if len(data) == expected_length:
+ public_key = data[:RNS.Identity.KEYSIZE//8]
+ interface_hash = data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8]
+ tunnel_id_data = public_key+interface_hash
+ tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
+ random_hash = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
+
+ signature = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8:expected_length]
+ signed_data = tunnel_id_data+random_hash
+
+ remote_transport_identity = RNS.Identity(create_keys=False)
+ remote_transport_identity.load_public_key(public_key)
+
+ if remote_transport_identity.validate(signature, signed_data):
+ Transport.handle_tunnel(tunnel_id, packet.receiving_interface)
+
+ except Exception as e:
+ RNS.log("An error occurred while validating tunnel establishment packet.", RNS.LOG_DEBUG)
+ RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
+*/
+}
+
+/*static*/ void Transport::handle_tunnel(const Bytes& tunnel_id, const Interface& interface) {
+// TODO
+/*p
+ expires = time.time() + Transport.DESTINATION_TIMEOUT
+ if not tunnel_id in Transport.tunnels:
+ RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG)
+ paths = {}
+ tunnel_entry = [tunnel_id, interface, paths, expires]
+ interface.tunnel_id = tunnel_id
+ Transport.tunnels[tunnel_id] = tunnel_entry
+ else:
+ RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG)
+ tunnel_entry = Transport.tunnels[tunnel_id]
+ tunnel_entry[1] = interface
+ tunnel_entry[3] = expires
+ interface.tunnel_id = tunnel_id
+ paths = tunnel_entry[2]
+
+ deprecated_paths = []
+ for destination_hash, path_entry in paths.items():
+ received_from = path_entry[1]
+ announce_hops = path_entry[2]
+ expires = path_entry[3]
+ random_blobs = path_entry[4]
+ receiving_interface = interface
+ packet = path_entry[6]
+ new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet]
+
+ should_add = False
+ if destination_hash in Transport.destination_table:
+ old_entry = Transport.destination_table[destination_hash]
+ old_hops = old_entry[2]
+ old_expires = old_entry[3]
+ if announce_hops <= old_hops or time.time() > old_expires:
+ should_add = True
+ else:
+ RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG)
+ else:
+ if time.time() < expires:
+ should_add = True
+ else:
+ RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because it has expired", RNS.LOG_DEBUG)
+
+ if should_add:
+ Transport.destination_table[destination_hash] = new_entry
+ RNS.log("Restored path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG)
+ else:
+ deprecated_paths.append(destination_hash)
+
+ for deprecated_path in deprecated_paths:
+ RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG)
+ paths.pop(deprecated_path)
+*/
+}
+
+/*static*/ void Transport::register_interface(Interface& interface) {
+ TRACE("Transport: Registering interface " + interface.get_hash().toHex() + " " + interface.toString());
+#if defined(INTERFACES_SET)
+ _interfaces.insert(interface);
+#elif defined(INTERFACES_LIST)
+ _interfaces.push_back(interface);
+#elif defined(INTERFACES_MAP)
+ _interfaces.insert({interface.get_hash(), interface});
+#endif
+ // CBA TODO set or add transport as listener on interface to receive incoming packets?
+}
+
+/*static*/ void Transport::deregister_interface(const Interface& interface) {
+ TRACE("Transport: Deregistering interface " + interface.toString());
+#if defined(INTERFACES_SET)
+ //for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) {
+ // if ((*iter).get() == interface) {
+ // _interfaces.erase(iter);
+ // TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString());
+ // break;
+ // }
+ //}
+ //auto iter = _interfaces.find(interface);
+ auto iter = _interfaces.find(const_cast<Interface&>(interface));
+ if (iter != _interfaces.end()) {
+ _interfaces.erase(iter);
+ TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString());
+ }
+#elif defined(INTERFACES_LIST)
+ for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) {
+ if ((*iter).get() == interface) {
+ _interfaces.erase(iter);
+ TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString());
+ break;
+ }
+ }
+#elif defined(INTERFACES_MAP)
+ auto iter = _interfaces.find(interface.get_hash());
+ if (iter != _interfaces.end()) {
+ TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).second.toString());
+ _interfaces.erase(iter);
+ }
+#endif
+}
+
+/*static*/ void Transport::register_destination(Destination& destination) {
+ //TRACE("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ TRACE("Transport: Registering destination " + destination.toString());
+ destination.mtu(Type::Reticulum::MTU);
+ if (destination.direction() == Type::Destination::IN) {
+#if defined(DESTINATIONS_SET)
+ for (auto& registered_destination : _destinations) {
+ if (destination.hash() == registered_destination.hash()) {
+ //p raise KeyError("Attempt to register an already registered destination.")
+ throw std::runtime_error("Attempt to register an already registered destination.");
+ }
+ }
+
+ // CBA ACCUMULATES
+ _destinations.insert(destination);
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(destination.hash());
+ if (iter != _destinations.end()) {
+ //p raise KeyError("Attempt to register an already registered destination.")
+ throw std::runtime_error("Attempt to register an already registered destination.");
+ }
+
+ // CBA ACCUMULATES
+ _destinations.insert({destination.hash(), destination});
+#endif
+
+ if (_owner && _owner.is_connected_to_shared_instance()) {
+ if (destination.type() == Type::Destination::SINGLE) {
+ TRACE("Transport:register_destination: Announcing destination " + destination.toString());
+ destination.announce({}, true);
+ }
+ }
+ }
+ else {
+ TRACE("Transport:register_destination: Skipping registration (not direction IN) of destination " + destination.toString());
+ }
+
+/*
+#if defined(DESTINATIONS_SET)
+ for (const Destination& destination : _destinations) {
+#elif defined(DESTINATIONS_MAP)
+ for (auto& [hash, destination] : _destinations) {
+#endif
+ TRACE("Transport::register_destination: Listed destination " + destination.toString());
+ }
+*/
+ //TRACE("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+}
+
+/*static*/ void Transport::deregister_destination(const Destination& destination) {
+ TRACE("Transport: Deregistering destination " + destination.toString());
+#if defined(DESTINATIONS_SET)
+ if (_destinations.find(destination) != _destinations.end()) {
+ _destinations.erase(destination);
+ TRACE("Transport::deregister_destination: Found and removed destination " + destination.toString());
+ }
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(destination.hash());
+ if (iter != _destinations.end()) {
+ _destinations.erase(iter);
+ TRACE("Transport::deregister_destination: Found and removed destination " + (*iter).second.toString());
+ }
+#endif
+}
+
+/*static*/ void Transport::register_link(Link& link) {
+ TRACE("Transport: Registering link " + link.toString());
+ if (link.initiator()) {
+ // CBA ACCUMULATES
+ _pending_links.insert(link);
+ }
+ else {
+ // CBA ACCUMULATES
+ _active_links.insert(link);
+ }
+}
+
+/*static*/ void Transport::activate_link(Link& link) {
+ TRACE("Transport: Activating link " + link.toString());
+ if (_pending_links.find(link) != _pending_links.end()) {
+ if (link.status() != Type::Link::ACTIVE) {
+ throw std::runtime_error("Invalid link state for link activation: " + std::to_string(link.status()));
+ }
+ _pending_links.erase(link);
+ // CBA ACCUMULATES
+ _active_links.insert(link);
+ link.status(Type::Link::ACTIVE);
+ }
+ else {
+ ERROR("Attempted to activate a link that was not in the pending table");
+ }
+}
+
+/*
+Registers an announce handler.
+
+:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)* callable. See the :ref:`Announce Example<example-announce>` for more info.
+*/
+/*static*/ void Transport::register_announce_handler(HAnnounceHandler handler) {
+ TRACE("Transport: Registering announce handler " + handler->aspect_filter());
+ _announce_handlers.insert(handler);
+}
+
+/*
+Deregisters an announce handler.
+
+:param handler: The announce handler to be deregistered.
+*/
+/*static*/ void Transport::deregister_announce_handler(HAnnounceHandler handler) {
+ TRACE("Transport: Deregistering announce handler " + handler->aspect_filter());
+ if (_announce_handlers.find(handler) != _announce_handlers.end()) {
+ _announce_handlers.erase(handler);
+ TRACE("Transport::deregister_announce_handler: Found and removed handler" + handler->aspect_filter());
+ }
+}
+
+/*static*/ Interface Transport::find_interface_from_hash(const Bytes& interface_hash) {
+#if defined(INTERFACES_SET)
+ for (const Interface& interface : _interfaces) {
+ if (interface.get_hash() == interface_hash) {
+ TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString());
+ return interface;
+ }
+ }
+#elif defined(INTERFACES_LIST)
+ for (Interface& interface : _interfaces) {
+ if (interface.get_hash() == interface_hash) {
+ TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString());
+ return interface;
+ }
+ }
+#elif defined(INTERFACES_MAP)
+ auto iter = _interfaces.find(interface_hash);
+ if (iter != _interfaces.end()) {
+ TRACE("Transport::find_interface_from_hash: Found interface " + (*iter).second.toString());
+ return (*iter).second;
+ }
+#endif
+
+ return {Type::NONE};
+}
+
+/*static*/ bool Transport::should_cache_packet(const Packet& packet) {
+ // TODO: Rework the caching system. It's currently
+ // not very useful to even cache Resource proofs,
+ // disabling it for now, until redesigned.
+ // if packet.context == RNS.Packet.RESOURCE_PRF:
+ // return True
+
+ return false;
+}
+
+// When caching packets to storage, they are written
+// exactly as they arrived over their interface. This
+// means that they have not had their hop count
+// increased yet! Take note of this when reading from
+// the packet cache.
+/*static*/ bool Transport::cache_packet(const Packet& packet, bool force_cache /*= false*/) {
+ TRACE("Checking to see if packet " + packet.get_hash().toHex() + " should be cached");
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ if (should_cache_packet(packet) || force_cache) {
+ TRACE("Saving packet " + packet.get_hash().toHex() + " to storage");
+ try {
+ char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet.get_hash().toHex().c_str());
+ return (Persistence::serialize(packet, packet_cache_path) > 0);
+ }
+ catch (std::exception& e) {
+ ERROR("Error writing packet to cache. The contained exception was: " + std::string(e.what()));
+ }
+ }
+#endif
+ return false;
+}
+
+/*static*/ Packet Transport::get_cached_packet(const Bytes& packet_hash) {
+ TRACE("Loading packet " + packet_hash.toHex() + " from cache storage");
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ try {
+/*p
+ packet_hash = RNS.hexrep(packet_hash, delimit=False)
+ path = RNS.Reticulum.cachepath+"/"+packet_hash
+
+ if os.path.isfile(path):
+ file = open(path, "rb")
+ cached_data = umsgpack.unpackb(file.read())
+ file.close()
+
+ packet = RNS.Packet(None, cached_data[0])
+ interface_reference = cached_data[1]
+
+ for interface in Transport.interfaces:
+ if str(interface) == interface_reference:
+ packet.receiving_interface = interface
+
+ return packet
+ else:
+ return None
+*/
+ char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet_hash.toHex().c_str());
+ Packet packet({Type::NONE});
+ if (Persistence::deserialize(packet, packet_cache_path) > 0) {
+ packet.unpack();
+ }
+ return packet;
+ }
+ catch (std::exception& e) {
+ ERROR("Exception occurred while getting cached packet.");
+ ERRORF("The contained exception was: %s", e.what());
+ }
+#endif
+ return {Type::NONE};
+}
+
+/*static*/ bool Transport::clear_cached_packet(const Bytes& packet_hash) {
+ TRACE("Clearing packet " + packet_hash.toHex() + " from cache storage");
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ try {
+ char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, packet_hash.toHex().c_str());
+ double start_time = OS::time();
+ bool success = RNS::Utilities::OS::remove_file(packet_cache_path);
+ double diff_time = OS::time() - start_time;
+ if (diff_time < 1.0) {
+ DEBUG("Remove cached packet in " + std::to_string((int)(diff_time*1000)) + " ms");
+ }
+ else {
+ DEBUG("Remove cached packet in " + std::to_string(diff_time) + " s");
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Exception occurred while clearing cached packet.");
+ ERRORF("The contained exception was: %s", e.what());
+ }
+#endif
+ return false;
+}
+
+/*static*/ bool Transport::cache_request_packet(const Packet& packet) {
+ if (packet.data().size() == Type::Identity::HASHLENGTH/8) {
+ const Packet& cached_packet = get_cached_packet(packet.data());
+
+ if (cached_packet) {
+ // If the packet was retrieved from the local
+ // cache, replay it to the Transport instance,
+ // so that it can be directed towards it original
+ // destination.
+ inbound(cached_packet.raw(), cached_packet.receiving_interface());
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+}
+
+/*static*/ void Transport::cache_request(const Bytes& packet_hash, const Destination& destination) {
+ const Packet& cached_packet = get_cached_packet(packet_hash);
+ if (cached_packet) {
+ // The packet was found in the local cache,
+ // replay it to the Transport instance.
+ inbound(cached_packet.raw(), cached_packet.receiving_interface());
+ }
+ else {
+ // The packet is not in the local cache,
+ // query the network.
+ Packet request(destination, packet_hash, Type::Packet::DATA, Type::Packet::CACHE_REQUEST);
+ request.send();
+ }
+}
+
+/*static*/ bool Transport::remove_path(const Bytes& destination_hash) {
+ if (_destination_table.erase(destination_hash) > 0) {
+ // CBA also remove cached announce packet if exists
+ }
+ return false;
+}
+
+/*
+:param destination_hash: A destination hash as *bytes*.
+:returns: *True* if a path to the destination is known, otherwise *False*.
+*/
+/*static*/ bool Transport::has_path(const Bytes& destination_hash) {
+ if (_destination_table.find(destination_hash) != _destination_table.end()) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+/*
+:param destination_hash: A destination hash as *bytes*.
+:returns: The number of hops to the specified destination, or ``RNS.Transport.PATHFINDER_M`` if the number of hops is unknown.
+*/
+/*static*/ uint8_t Transport::hops_to(const Bytes& destination_hash) {
+ auto iter = _destination_table.find(destination_hash);
+ if (iter != _destination_table.end()) {
+ DestinationEntry destination_entry = (*iter).second;
+ return destination_entry._hops;
+ }
+ else {
+ return PATHFINDER_M;
+ }
+}
+
+/*
+:param destination_hash: A destination hash as *bytes*.
+:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown.
+*/
+/*static*/ Bytes Transport::next_hop(const Bytes& destination_hash) {
+ auto iter = _destination_table.find(destination_hash);
+ if (iter != _destination_table.end()) {
+ DestinationEntry destination_entry = (*iter).second;
+ return destination_entry._received_from;
+ }
+ else {
+ return {};
+ }
+}
+
+/*
+:param destination_hash: A destination hash as *bytes*.
+:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown.
+*/
+/*static*/ Interface Transport::next_hop_interface(const Bytes& destination_hash) {
+ auto iter = _destination_table.find(destination_hash);
+ if (iter != _destination_table.end()) {
+ DestinationEntry destination_entry = (*iter).second;
+ return destination_entry.receiving_interface();
+ }
+ else {
+ return {Type::NONE};
+ }
+}
+
+/*static*/ uint32_t Transport::next_hop_interface_bitrate(const Bytes& destination_hash) {
+ const Interface& interface = next_hop_interface(destination_hash);
+ if (interface) {
+ return interface.bitrate();
+ }
+ else {
+ return 0;
+ }
+}
+
+/*static*/ uint16_t Transport::next_hop_interface_hw_mtu(const Bytes& destination_hash) {
+ const Interface& interface = next_hop_interface(destination_hash);
+ if (interface) {
+ if (interface.AUTOCONFIGURE_MTU() || interface.FIXED_MTU()) return interface.HW_MTU();
+ else return 0;
+ }
+ else {
+ return 0;
+ }
+}
+
+/*static*/ double Transport::next_hop_per_bit_latency(const Bytes& destination_hash) {
+ uint32_t bitrate = next_hop_interface_bitrate(destination_hash);
+ if (bitrate > 0) {
+ return (1.0/(double)bitrate);
+ }
+ else {
+ return 0.0;
+ }
+}
+
+/*static*/ double Transport::next_hop_per_byte_latency(const Bytes& destination_hash) {
+ double per_bit_latency = next_hop_per_bit_latency(destination_hash);
+ if (per_bit_latency > 0.0) {
+ return per_bit_latency*8.0;
+ }
+ else {
+ return 0.0;
+ }
+}
+
+/*static*/ double Transport::first_hop_timeout(const Bytes& destination_hash) {
+ double latency = next_hop_per_byte_latency(destination_hash);
+ if (latency > 0.0) {
+ return RNS::Type::Reticulum::MTU * latency + RNS::Type::Reticulum::DEFAULT_PER_HOP_TIMEOUT;
+ }
+ else {
+ return RNS::Type::Reticulum::DEFAULT_PER_HOP_TIMEOUT;
+ }
+}
+
+/*static*/ double Transport::extra_link_proof_timeout(const Interface& interface) {
+ if (interface) {
+ return ((1.0/(double)interface.bitrate())*8.0)*RNS::Type::Reticulum::MTU;
+ }
+ else {
+ return 0.0;
+ }
+}
+
+/*static*/ bool Transport::expire_path(const Bytes& destination_hash) {
+ auto iter = _destination_table.find(destination_hash);
+ if (iter != _destination_table.end()) {
+ DestinationEntry destination_entry = (*iter).second;
+ destination_entry._timestamp = 0;
+ _tables_last_culled = 0;
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+/*p
+
+ @staticmethod
+ def mark_path_unresponsive(destination_hash):
+ if destination_hash in Transport.destination_table:
+ Transport.path_states[destination_hash] = Transport.STATE_UNRESPONSIVE
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def mark_path_responsive(destination_hash):
+ if destination_hash in Transport.destination_table:
+ Transport.path_states[destination_hash] = Transport.STATE_RESPONSIVE
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def mark_path_unknown_state(destination_hash):
+ if destination_hash in Transport.destination_table:
+ Transport.path_states[destination_hash] = Transport.STATE_UNKNOWN
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def path_is_unresponsive(destination_hash):
+ if destination_hash in Transport.path_states:
+ if Transport.path_states[destination_hash] == Transport.STATE_UNRESPONSIVE:
+ return True
+
+ return False
+
+*/
+
+/*
+Requests a path to the destination from the network. If
+another reachable peer on the network knows a path, it
+will announce it.
+
+:param destination_hash: A destination hash as *bytes*.
+:param on_interface: If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used.
+*/
+///*static*/ void Transport::request_path(const Bytes& destination_hash, const Interface& on_interface /*= {Type::NONE}*/, const Bytes& tag /*= {}*/, bool recursive /*= false*/) {
+/*static*/ void Transport::request_path(const Bytes& destination_hash, const Interface& on_interface, const Bytes& tag /*= {}*/, bool recursive /*= false*/) {
+ Bytes request_tag;
+ if (!tag) {
+ request_tag = Identity::get_random_hash();
+ }
+ else {
+ request_tag = tag;
+ }
+
+ Bytes path_request_data;
+ if (Reticulum::transport_enabled()) {
+ path_request_data = destination_hash + _identity.hash() + request_tag;
+ }
+ else {
+ path_request_data = destination_hash + request_tag;
+ }
+
+ Destination path_request_dst({Type::NONE}, Type::Destination::OUT, Type::Destination::PLAIN, Type::Transport::APP_NAME, "path.request");
+ Packet packet(path_request_dst, on_interface, path_request_data, Type::Packet::DATA, Type::Packet::CONTEXT_NONE, Type::Transport::BROADCAST, Type::Packet::HEADER_1);
+
+ if (on_interface && recursive) {
+// TODO
+/*p
+ if not hasattr(on_interface, "announce_cap"):
+ on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
+
+ if not hasattr(on_interface, "announce_allowed_at"):
+ on_interface.announce_allowed_at = 0
+
+ if not hasattr(on_interface, "announce_queue"):
+ on_interface.announce_queue = []
+*/
+
+ bool queued_announces = (on_interface.announce_queue().size() > 0);
+ if (queued_announces) {
+ TRACE("Blocking recursive path request on " + on_interface.toString() + " due to queued announces");
+ return;
+ }
+ else {
+ double now = OS::time();
+ if (now < on_interface.announce_allowed_at()) {
+ TRACE("Blocking recursive path request on " + on_interface.toString() + " due to active announce cap");
+ return;
+ }
+ else {
+ //p tx_time = ((len(path_request_data)+RNS.Reticulum.HEADER_MINSIZE)*8) / on_interface.bitrate
+ uint32_t wait_time = 0;
+ if ( on_interface.bitrate() > 0 && on_interface.announce_cap() > 0) {
+ uint32_t tx_time = ((path_request_data.size() + Type::Reticulum::HEADER_MINSIZE)*8) / on_interface.bitrate();
+ wait_time = (tx_time / on_interface.announce_cap());
+ }
+ const_cast<Interface&>(on_interface).announce_allowed_at(now + wait_time);
+ }
+ }
+ }
+
+ packet.send();
+ _path_requests[destination_hash] = OS::time();
+}
+
+/*static*/ void Transport::request_path(const Bytes& destination_hash) {
+ return request_path(destination_hash, {Type::NONE});
+}
+
+/*static*/ void Transport::path_request_handler(const Bytes& data, const Packet& packet) {
+ TRACE("Transport::path_request_handler");
+ try {
+ // If there is at least bytes enough for a destination
+ // hash in the packet, we assume those bytes are the
+ // destination being requested.
+ if (data.size() >= Type::Identity::TRUNCATED_HASHLENGTH/8) {
+ Bytes destination_hash = data.left(Type::Identity::TRUNCATED_HASHLENGTH/8);
+ //TRACE("Transport::path_request_handler: destination_hash: " + destination_hash.toHex());
+ // If there is also enough bytes for a transport
+ // instance ID and at least one tag byte, we
+ // assume the next bytes to be the trasport ID
+ // of the requesting transport instance.
+ Bytes requesting_transport_instance;
+ if (data.size() > (Type::Identity::TRUNCATED_HASHLENGTH/8)*2) {
+ requesting_transport_instance = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8, Type::Identity::TRUNCATED_HASHLENGTH/8);
+ //TRACE("Transport::path_request_handler: requesting_transport_instance: " + requesting_transport_instance.toHex());
+ }
+
+ Bytes tag_bytes;
+ if (data.size() > Type::Identity::TRUNCATED_HASHLENGTH/8*2) {
+ tag_bytes = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8*2);
+ }
+ else if (data.size() > Type::Identity::TRUNCATED_HASHLENGTH/8) {
+ tag_bytes = data.mid(Type::Identity::TRUNCATED_HASHLENGTH/8);
+ }
+
+ if (tag_bytes) {
+ //TRACE("Transport::path_request_handler: tag_bytes: " + tag_bytes.toHex());
+ if (tag_bytes.size() > Type::Identity::TRUNCATED_HASHLENGTH/8) {
+ tag_bytes = tag_bytes.left(Type::Identity::TRUNCATED_HASHLENGTH/8);
+ }
+
+ Bytes unique_tag = destination_hash + tag_bytes;
+ //TRACE("Transport::path_request_handler: unique_tag: " + unique_tag.toHex());
+
+ if (_discovery_pr_tags.find(unique_tag) == _discovery_pr_tags.end()) {
+ // CBA ACCUMULATES
+ _discovery_pr_tags.insert(unique_tag);
+
+ path_request(
+ destination_hash,
+ from_local_client(packet),
+ packet.receiving_interface(),
+ requesting_transport_instance,
+ tag_bytes
+ );
+ }
+ else {
+ DEBUG("Ignoring duplicate path request for " + destination_hash.toHex() + " with tag " + unique_tag.toHex());
+ }
+ }
+ else {
+ DEBUG("Ignoring tagless path request for " + destination_hash.toHex());
+ }
+ }
+ }
+ catch (std::exception& e) {
+ ERROR("Error while handling path request. The contained exception was: " + std::string(e.what()));
+ }
+}
+
+/*static*/ void Transport::path_request(const Bytes& destination_hash, bool is_from_local_client, const Interface& attached_interface, const Bytes& requestor_transport_id /*= {}*/, const Bytes& tag /*= {}*/) {
+ TRACE("Transport::path_request");
+ bool should_search_for_unknown = false;
+ std::string interface_str;
+
+ if (attached_interface) {
+ if (Reticulum::transport_enabled() && (attached_interface.mode() & Interface::DISCOVER_PATHS_FOR) > 0) {
+ TRACE("Transport::path_request_handler: interface allows searching for unknown paths");
+ should_search_for_unknown = true;
+ }
+
+ interface_str = " on " + attached_interface.toString();
+ }
+
+ DEBUG("Path request for destination " + destination_hash.toHex() + interface_str);
+
+ bool destination_exists_on_local_client = false;
+ if (_local_client_interfaces.size() > 0) {
+ auto iter = _destination_table.find(destination_hash);
+ if (iter != _destination_table.end()) {
+ TRACE("Transport::path_request_handler: entry found for destination " + destination_hash.toHex());
+ DestinationEntry& destination_entry = (*iter).second;
+ if (is_local_client_interface(destination_entry.receiving_interface())) {
+ destination_exists_on_local_client = true;
+ // CBA ACCUMULATES
+ _pending_local_path_requests.insert({destination_hash, attached_interface});
+ }
+ }
+ else {
+ TRACE("Transport::path_request_handler: entry not found for destination " + destination_hash.toHex());
+ }
+ }
+
+ auto destination_iter = _destination_table.find(destination_hash);
+ //local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
+#if defined(DESTINATIONS_SET)
+ Destination local_destination({Type::NONE});
+ for (auto& destination : _destinations) {
+ if (destination.hash() == destination_hash) {
+ local_destination = destination;
+ break;
+ }
+ }
+ //if local_destination != None:
+ if (local_destination) {
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(destination_hash);
+ if (iter != _destinations.end()) {
+ auto& local_destination = (*iter).second;
+#endif
+ local_destination.announce({Bytes::NONE}, true, attached_interface, tag);
+ DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", destination is local to this system");
+ }
+ //p elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.destination_table):
+ else if ((Reticulum::transport_enabled() || is_from_local_client) && destination_iter != _destination_table.end()) {
+ TRACE("Transport::path_request_handler: entry found for destination " + destination_hash.toHex());
+ DestinationEntry& destination_entry = (*destination_iter).second;
+ const Packet& announce_packet = destination_entry.announce_packet();
+ const Bytes& next_hop = destination_entry._received_from;
+ const Interface& receiving_interface = destination_entry.receiving_interface();
+
+ if (attached_interface.mode() == Type::Interface::MODE_ROAMING && attached_interface == receiving_interface) {
+ DEBUG("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface");
+ }
+ else {
+ if (requestor_transport_id && destination_entry._received_from == requestor_transport_id) {
+ // TODO: Find a bandwidth efficient way to invalidate our
+ // known path on this signal. The obvious way of signing
+ // path requests with transport instance keys is quite
+ // inefficient. There is probably a better way. Doing
+ // path invalidation here would decrease the network
+ // convergence time. Maybe just drop it?
+ DEBUG("Not answering path request for destination " + destination_hash.toHex() + interface_str + ", since next hop is the requestor");
+ }
+ else {
+ DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", path is known");
+
+ double now = OS::time();
+ uint8_t retries = Type::Transport::PATHFINDER_R;
+ uint8_t local_rebroadcasts = 0;
+ bool block_rebroadcasts = true;
+ // CBA TODO Determine if okay to take hops directly from DestinationEntry
+ uint8_t announce_hops = announce_packet.hops();
+
+ double retransmit_timeout = 0;
+ if (is_from_local_client) {
+ retransmit_timeout = now;
+ }
+ else {
+ // TODO: Look at this timing
+ retransmit_timeout = now + Type::Transport::PATH_REQUEST_GRACE /*+ (RNS.rand() * Transport.PATHFINDER_RW)*/;
+ }
+
+ // This handles an edge case where a peer sends a past
+ // request for a destination just after an announce for
+ // said destination has arrived, but before it has been
+ // rebroadcast locally. In such a case the actual announce
+ // is temporarily held, and then reinserted when the path
+ // request has been served to the peer.
+ auto announce_iter = _announce_table.find(announce_packet.destination_hash());
+ if (announce_iter != _announce_table.end()) {
+ AnnounceEntry& held_entry = (*announce_iter).second;
+ // CBA ACCUMULATES
+ _held_announces.insert({announce_packet.destination_hash(), held_entry});
+ }
+
+/*
+ // CBA ACCUMULATES
+ _announce_table.insert({announce_packet.destination_hash(), {
+ now,
+ retransmit_timeout,
+ retries,
+ // BUG?
+ //destination_entry.receiving_interface,
+ destination_entry._received_from,
+ announce_hops,
+ announce_packet,
+ local_rebroadcasts,
+ block_rebroadcasts,
+ attached_interface
+ }});
+*/
+ AnnounceEntry announce_entry(
+ now,
+ retransmit_timeout,
+ retries,
+ // BUG?
+ //destination_entry.receiving_interface,
+ destination_entry._received_from,
+ announce_hops,
+ announce_packet,
+ local_rebroadcasts,
+ block_rebroadcasts,
+ attached_interface
+ );
+ // CBA ACCUMULATES
+ _announce_table.insert({announce_packet.destination_hash(), announce_entry});
+ }
+ }
+ }
+ else if (is_from_local_client) {
+ // Forward path request on all interfaces
+ // except the local client
+ DEBUG("Forwarding path request from local client for destination " + destination_hash.toHex() + interface_str + " to all other interfaces");
+ Bytes request_tag = Identity::get_random_hash();
+#if defined(INTERFACES_SET)
+ for (const Interface& interface : _interfaces) {
+#elif defined(INTERFACES_LIST)
+ for (Interface& interface : _interfaces) {
+#elif defined(INTERFACES_MAP)
+ for (auto& [hash, interface] : _interfaces) {
+#endif
+ if (interface != attached_interface) {
+ request_path(destination_hash, interface, request_tag);
+ }
+ }
+ }
+ else if (should_search_for_unknown) {
+ TRACE("Transport::path_request_handler: searching for unknown path to " + destination_hash.toHex());
+ if (_discovery_path_requests.find(destination_hash) != _discovery_path_requests.end()) {
+ DEBUG("There is already a waiting path request for destination " + destination_hash.toHex() + " on behalf of path request" + interface_str);
+ }
+ else {
+ // Forward path request on all interfaces
+ // except the requestor interface
+ DEBUG("Attempting to discover unknown path to destination " + destination_hash.toHex() + " on behalf of path request" + interface_str);
+ //p pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface }
+ //p _discovery_path_requests[destination_hash] = pr_entry;
+ // CBA ACCUMULATES
+ _discovery_path_requests.insert({destination_hash, {
+ destination_hash,
+ OS::time() + Type::Transport::PATH_REQUEST_TIMEOUT,
+ attached_interface
+ }});
+
+#if defined(BOUNDARY_MODE)
+ // BOUNDARY: Track this destination in Whitelist 2 so the path
+ // response announce from the backbone will be allowed through
+ _boundary_mentioned_addresses.insert(destination_hash);
+#endif
+
+#if defined(INTERFACES_SET)
+ for (const Interface& interface : _interfaces) {
+#elif defined(INTERFACES_LIST)
+ for (Interface& interface : _interfaces) {
+#elif defined(INTERFACES_MAP)
+ for (auto& [hash, interface] : _interfaces) {
+#endif
+ // CBA EXPERIMENTAL forwarding path requests even on requestor interface in order to support
+ // path-finding over LoRa mesh
+ //if (interface != attached_interface) {
+ if (true) {
+ TRACE("Transport::path_request: requesting path on interface " + interface.toString());
+ // Use the previously extracted tag from this path request
+ // on the new path requests as well, to avoid potential loops
+ request_path(destination_hash, interface, tag, true);
+ }
+ else {
+ TRACE("Transport::path_request: not requesting path on same interface " + interface.toString());
+ }
+ }
+ }
+ }
+ else if (!is_from_local_client && _local_client_interfaces.size() > 0) {
+ // Forward the path request on all local
+ // client interfaces
+ DEBUG("Forwarding path request for destination " + destination_hash.toHex() + interface_str + " to local clients");
+ for (const Interface& interface : _local_client_interfaces) {
+ request_path(destination_hash, interface);
+ }
+ }
+ else {
+ DEBUG("Ignoring path request for destination " + destination_hash.toHex() + interface_str + ", no path known");
+ }
+}
+
+/*static*/ bool Transport::from_local_client(const Packet& packet) {
+ if (packet.receiving_interface().parent_interface()) {
+ return is_local_client_interface(packet.receiving_interface());
+ }
+ else {
+ return false;
+ }
+}
+
+/*static*/ bool Transport::is_local_client_interface(const Interface& interface) {
+ if (interface.parent_interface()) {
+ if (interface.parent_interface()->is_local_shared_instance()) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+}
+
+/*static*/ bool Transport::interface_to_shared_instance(const Interface& interface) {
+ if (interface.is_connected_to_shared_instance()) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+/*static*/ void Transport::detach_interfaces() {
+// TODO
+/*p
+ detachable_interfaces = []
+
+ for interface in Transport.interfaces:
+ // Currently no rules are being applied
+ // here, and all interfaces will be sent
+ // the detach call on RNS teardown.
+ if True:
+ detachable_interfaces.append(interface)
+ else:
+ pass
+
+ for interface in Transport.local_client_interfaces:
+ // Currently no rules are being applied
+ // here, and all interfaces will be sent
+ // the detach call on RNS teardown.
+ if True:
+ detachable_interfaces.append(interface)
+ else:
+ pass
+
+ for interface in detachable_interfaces:
+ interface.detach()
+*/
+}
+
+/*static*/ void Transport::shared_connection_disappeared() {
+// TODO
+/*p
+ for link in Transport.active_links:
+ link.teardown()
+
+ for link in Transport.pending_links:
+ link.teardown()
+
+ Transport.announce_table = {}
+ Transport.destination_table = {}
+ Transport.reverse_table = {}
+ Transport.link_table = {}
+ Transport.held_announces = {}
+ Transport.announce_handlers = []
+ Transport.tunnels = {}
+*/
+}
+
+/*static*/ void Transport::shared_connection_reappeared() {
+// TODO
+/*p
+ if Transport.owner.is_connected_to_shared_instance:
+ for registered_destination in Transport.destinations:
+ if registered_destination.type == RNS.Destination.SINGLE:
+ registered_destination.announce(path_response=True)
+*/
+}
+
+/*static*/ void Transport::drop_announce_queues() {
+// TODO
+/*p
+ for interface in Transport.interfaces:
+ if hasattr(interface, "announce_queue") and interface.announce_queue != None:
+ na = len(interface.announce_queue)
+ if na > 0:
+ if na == 1:
+ na_str = "1 announce"
+ else:
+ na_str = str(na)+" announces"
+
+ interface.announce_queue = []
+ RNS.log("Dropped "+na_str+" on "+str(interface), RNS.LOG_VERBOSE)
+*/
+}
+
+/*static*/ uint64_t Transport::announce_emitted(const Packet& packet) {
+ //p random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
+ //p announce_emitted = int.from_bytes(random_blob[5:10], "big")
+ Bytes random_blob = packet.data().mid(RNS::Type::Identity::KEYSIZE/8+RNS::Type::Identity::NAME_HASH_LENGTH/8, 10);
+ if (random_blob) {
+ return OS::from_bytes_big_endian(random_blob.data() + 5, 5);
+ }
+ return 0;
+}
+
+/*static*/ void Transport::write_packet_hashlist() {
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+// TODO
+/*p
+ if not Transport.owner.is_connected_to_shared_instance:
+ if hasattr(Transport, "saving_packet_hashlist"):
+ wait_interval = 0.2
+ wait_timeout = 5
+ wait_start = time.time()
+ while Transport.saving_packet_hashlist:
+ time.sleep(wait_interval)
+ if time.time() > wait_start+wait_timeout:
+ RNS.log("Could not save packet hashlist to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR)
+ return False
+
+ try:
+ Transport.saving_packet_hashlist = True
+ save_start = time.time()
+
+ if not RNS.Reticulum.transport_enabled():
+ Transport.packet_hashlist = []
+ else:
+ RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG)
+
+ packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
+ file = open(packet_hashlist_path, "wb")
+ file.write(umsgpack.packb(Transport.packet_hashlist))
+ file.close()
+
+ save_time = time.time() - save_start
+ if save_time < 1:
+ time_str = str(round(save_time*1000,2))+"ms"
+ else:
+ time_str = str(round(save_time,2))+"s"
+ RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG)
+
+ except Exception as e:
+ RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
+
+ Transport.saving_packet_hashlist = False
+*/
+#endif
+}
+
+//#define CUSTOM 1
+
+/*static*/ bool Transport::read_path_table() {
+ DEBUG("Transport::read_path_table");
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath);
+ if (!_owner.is_connected_to_shared_instance() && OS::file_exists(destination_table_path)) {
+/*p
+ serialised_destinations = []
+ try:
+ file = open(destination_table_path, "rb")
+ serialised_destinations = umsgpack.unpackb(file.read())
+ file.close()
+
+ for serialised_entry in serialised_destinations:
+ destination_hash = serialised_entry[0]
+
+ if len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
+ timestamp = serialised_entry[1]
+ received_from = serialised_entry[2]
+ hops = serialised_entry[3]
+ expires = serialised_entry[4]
+ random_blobs = serialised_entry[5]
+ receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
+ announce_packet = Transport.get_cached_packet(serialised_entry[7])
+
+ if announce_packet != None and receiving_interface != None:
+ announce_packet.unpack()
+ // We increase the hops, since reading a packet
+ // from cache is equivalent to receiving it again
+ // over an interface. It is cached with it's non-
+ // increased hop-count.
+ announce_packet.hops += 1
+ Transport.destination_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
+ RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG)
+ else:
+ RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
+ if announce_packet == None:
+ RNS.log("The announce packet could not be loaded from cache", RNS.LOG_DEBUG)
+ if receiving_interface == None:
+ RNS.log("The interface is no longer available", RNS.LOG_DEBUG)
+*/
+
+ try {
+#if CUSTOM
+TRACEF("Transport::start: buffer capacity %d bytes", Persistence::_buffer.capacity());
+ if (RNS::Utilities::OS::read_file(destination_table_path, Persistence::_buffer) > 0) {
+ TRACEF("Transport::start: read: %d bytes", Persistence::_buffer.size());
+#ifndef NDEBUG
+ // CBA DEBUG Dump path table
+TRACEF("Transport::start: buffer addr: 0x%X", Persistence::_buffer.data());
+TRACEF("Transport::start: buffer size %d bytes", Persistence::_buffer.size());
+ //TRACE("SERIALIZED: destination_table");
+ //TRACE(Persistence::_buffer.toString());
+#endif
+#ifdef USE_MSGPACK
+ DeserializationError error = deserializeMsgPack(Persistence::_document, Persistence::_buffer.data());
+#else
+ DeserializationError error = deserializeJson(Persistence::_document, Persistence::_buffer.data());
+#endif
+ TRACEF("Transport::start: doc size: %d bytes", Persistence::_buffer.size());
+ if (!error) {
+ // Calculate crc for dirty-checking before write
+ _destination_table_crc = Crc::crc32(0, Persistence::_buffer.data(), Persistence::_buffer.size());
+ _destination_table = Persistence::_document.as<std::map<Bytes, DestinationEntry>>();
+#else // CUSTOM
+ // Calculate crc for dirty-checking before write
+ if (Persistence::deserialize(_destination_table, destination_table_path, _destination_table_crc) > 0) {
+#endif // CUSTOM
+
+ TRACEF("Transport::start: successfully deserialized path table with %d entries", _destination_table.size());
+ std::vector<Bytes> invalid_paths;
+ for (auto& [destination_hash, destination_entry] : _destination_table) {
+#ifndef NDEBUG
+ TRACEF("Transport::start: entry: %s = %s", destination_hash.toHex().c_str(), destination_entry.debugString().c_str());
+#endif
+ // CBA If announce packet load fails then remove destination entry (it's useless without announce packet)
+ if (!destination_entry.announce_packet()) {
+ // remove destination
+ WARNINGF("Transport::start: removing invalid path to %s due to missing announce packet", destination_hash.toHex().c_str());
+ invalid_paths.push_back(destination_hash);
+ }
+ // CBA If receiving interface is not found then remove destination entry (it's useless without interface)
+ if (!destination_entry.receiving_interface()) {
+ // remove destination
+ WARNINGF("Transport::start: removing invalid path to %s due to missing receiving interface", destination_hash.toHex().c_str());
+ invalid_paths.push_back(destination_hash);
+ }
+ }
+ for (const auto& destination_hash : invalid_paths) {
+ _destination_table.erase(destination_hash);
+ }
+ return true;
+ }
+ else {
+ TRACE("Transport::start: failed to deserialize");
+ }
+#if CUSTOM
+ }
+ else {
+ TRACE("Transport::start: destination table read failed");
+ }
+#else // CUSTOM
+#endif // CUSTOM
+
+ VERBOSEF("Loaded %d valid path table entries from storage", _destination_table.size());
+
+ }
+ catch (std::exception& e) {
+ ERRORF("Could not load destination table from storage, the contained exception was: %s", e.what());
+ }
+ }
+#endif
+ return false;
+}
+
+/*static*/ bool Transport::write_path_table() {
+ DEBUG("Transport::write_path_table");
+
+ if (Transport::_owner.is_connected_to_shared_instance()) {
+ return true;
+ }
+
+ bool success = false;
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ if (_saving_path_table) {
+ double wait_interval = 0.2;
+ double wait_timeout = 5;
+ double wait_start = OS::time();
+ while (_saving_path_table) {
+ OS::sleep(wait_interval);
+ if (OS::time() > (wait_start + wait_timeout)) {
+ ERROR("Could not save path table to storage, waiting for previous save operation timed out.");
+ return false;
+ }
+ }
+ }
+
+ try {
+ _saving_path_table = true;
+ double save_start = OS::time();
+ DEBUGF("Saving %d path table entries to storage...", _destination_table.size());
+
+/*p
+ serialised_destinations = []
+ for destination_hash in Transport.destination_table:
+ // Get the destination entry from the destination table
+ de = Transport.destination_table[destination_hash]
+ interface_hash = de[5].get_hash()
+
+ // Only store destination table entry if the associated
+ // interface is still active
+ interface = Transport.find_interface_from_hash(interface_hash)
+ if interface != None:
+ // Get the destination entry from the destination table
+ de = Transport.destination_table[destination_hash]
+ timestamp = de[0]
+ received_from = de[1]
+ hops = de[2]
+ expires = de[3]
+ random_blobs = de[4]
+ packet_hash = de[6].get_hash()
+
+ serialised_entry = [
+ destination_hash,
+ timestamp,
+ received_from,
+ hops,
+ expires,
+ random_blobs,
+ interface_hash,
+ packet_hash
+ ]
+
+ serialised_destinations.append(serialised_entry)
+
+ Transport.cache(de[6], force_cache=True)
+
+ destination_table_path = RNS.Reticulum.storagepath+"/destination_table"
+ file = open(destination_table_path, "wb")
+ file.write(umsgpack.packb(serialised_destinations))
+ file.close()
+*/
+
+#if CUSTOM
+ {
+ Persistence::_document.set(_destination_table);
+ TRACEF("Transport::write_path_table: doc size %d bytes", Persistence::_document.memoryUsage());
+
+ //size_t size = 8192;
+ size_t size = Persistence::_buffer.capacity();
+TRACE("Transport::write_path_table: obtaining buffer size " + std::to_string(size) + " bytes");
+ uint8_t* buffer = Persistence::_buffer.writable(size);
+TRACE("Transport::write_path_table: buffer addr: " + std::to_string((long)buffer));
+#ifdef USE_MSGPACK
+ size_t length = serializeMsgPack(Persistence::_document, buffer, size);
+#else
+ size_t length = serializeJson(Persistence::_document, buffer, size);
+#endif
+ TRACEF("Transport::write_path_table: serialized %d bytes", length);
+ if (length < size) {
+ Persistence::_buffer.resize(length);
+ }
+ }
+ if (Persistence::_buffer.size() > 0) {
+ char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath);
+#ifndef NDEBUG
+ // CBA DEBUG Dump path table
+TRACE("Transport::write_path_table: buffer addr: " + std::to_string((long)Persistence::_buffer.data()));
+TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence::_buffer.size()) + " bytes");
+ //TRACE("SERIALIZED: destination_table");
+ //TRACE(Persistence::_buffer.toString());
+#endif
+ // Check crc to see if data has changed before writing
+ uint32_t crc = Crc::crc32(0, Persistence::_buffer.data(), Persistence::_buffer.size());
+ if (_destination_table_crc > 0 && crc == _destination_table_crc) {
+ TRACE("Transport::write_path_table: no change detected, skipping write");
+ }
+ else if (RNS::Utilities::OS::write_file(destination_table_path, Persistence::_buffer) == Persistence::_buffer.size()) {
+ TRACEF("Transport::write_path_table: wrote %d entries, %d bytes", _destination_table.size(), Persistence::_buffer.size());
+ _destination_table_crc = crc;
+ success = true;
+
+#ifndef NDEBUG
+ // CBA DEBUG Dump path table
+ //TRACE("FILE: destination_table");
+ //if (OS::read_file("/destination_table", Persistence::_buffer) > 0) {
+ // TRACE(Persistence::_buffer.toString());
+ //}
+#endif
+ }
+ else {
+ TRACE("Transport::write_path_table: write failed");
+ }
+ }
+ else {
+ TRACE("Transport::write_path_table: failed to serialize");
+ }
+#else // CUSTOM
+ uint32_t crc = Persistence::crc(_destination_table);
+ if (_destination_table_crc > 0 && crc == _destination_table_crc) {
+ TRACE("Transport::write_path_table: no change detected, skipping write");
+ }
+ else {
+ TRACE("Transport::write_path_table: change detected, writing...");
+ char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath);
+ if (Persistence::serialize(_destination_table, destination_table_path, _destination_table_crc) > 0) {
+ TRACEF("Transport::write_path_table: wrote %d entries, %d bytes", _destination_table.size(), Persistence::_buffer.size());
+ success = true;
+ }
+ }
+#endif // CUSTOM
+
+ if (success) {
+ double save_time = OS::time() - save_start;
+ if (save_time < 1.0) {
+ //DEBUG("Saved " + std::to_string(_destination_table.size()) + " path table entries in " + std::to_string(OS::round(save_time * 1000, 1)) + " ms");
+ DEBUGF("Saved %d path table entries in %d ms", _destination_table.size(), (int)(save_time*1000));
+ }
+ else {
+ //DEBUG("Saved " + std::to_string(_destination_table.size()) + " path table entries in " + std::to_string(OS::round(save_time, 1)) + " s");
+ DEBUGF("Saved %d path table entries in %d s", _destination_table.size(), save_time);
+ }
+ }
+ }
+ catch (std::exception& e) {
+ ERRORF("Could not save path table to storage, the contained exception was: %s", e.what());
+ }
+#endif
+
+ _saving_path_table = false;
+
+ return success;
+}
+
+/*static*/ void Transport::read_tunnel_table() {
+ DEBUG("Transport::read_tunnel_table");
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+// TODO
+/*p
+ tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
+ if os.path.isfile(tunnel_table_path) and not Transport.owner.is_connected_to_shared_instance:
+ serialised_tunnels = []
+ try:
+ file = open(tunnel_table_path, "rb")
+ serialised_tunnels = umsgpack.unpackb(file.read())
+ file.close()
+
+ for serialised_tunnel in serialised_tunnels:
+ tunnel_id = serialised_tunnel[0]
+ interface_hash = serialised_tunnel[1]
+ serialised_paths = serialised_tunnel[2]
+ expires = serialised_tunnel[3]
+
+ tunnel_paths = {}
+ for serialised_entry in serialised_paths:
+ destination_hash = serialised_entry[0]
+ timestamp = serialised_entry[1]
+ received_from = serialised_entry[2]
+ hops = serialised_entry[3]
+ expires = serialised_entry[4]
+ random_blobs = serialised_entry[5]
+ receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
+ announce_packet = Transport.get_cached_packet(serialised_entry[7])
+
+ if announce_packet != None:
+ announce_packet.unpack()
+ // We increase the hops, since reading a packet
+ // from cache is equivalent to receiving it again
+ // over an interface. It is cached with it's non-
+ // increased hop-count.
+ announce_packet.hops += 1
+
+ tunnel_path = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
+ tunnel_paths[destination_hash] = tunnel_path
+
+ tunnel = [tunnel_id, None, tunnel_paths, expires]
+ Transport.tunnels[tunnel_id] = tunnel
+
+ if len(Transport.destination_table) == 1:
+ specifier = "entry"
+ else:
+ specifier = "entries"
+
+ RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE)
+
+ except Exception as e:
+ RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
+*/
+#endif
+}
+
+/*static*/ void Transport::write_tunnel_table() {
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+// TODO
+/*p
+ if not Transport.owner.is_connected_to_shared_instance:
+ if hasattr(Transport, "saving_tunnel_table"):
+ wait_interval = 0.2
+ wait_timeout = 5
+ wait_start = time.time()
+ while Transport.saving_tunnel_table:
+ time.sleep(wait_interval)
+ if time.time() > wait_start+wait_timeout:
+ RNS.log("Could not save tunnel table to storage, waiting for previous save operation timed out.", RNS.LOG_ERROR)
+ return False
+
+ try:
+ Transport.saving_tunnel_table = True
+ save_start = time.time()
+ RNS.log("Saving tunnel table to storage...", RNS.LOG_DEBUG)
+
+ serialised_tunnels = []
+ for tunnel_id in Transport.tunnels:
+ te = Transport.tunnels[tunnel_id]
+ interface = te[1]
+ tunnel_paths = te[2]
+ expires = te[3]
+
+ if interface != None:
+ interface_hash = interface.get_hash()
+ else:
+ interface_hash = None
+
+ serialised_paths = []
+ for destination_hash in tunnel_paths:
+ de = tunnel_paths[destination_hash]
+
+ timestamp = de[0]
+ received_from = de[1]
+ hops = de[2]
+ expires = de[3]
+ random_blobs = de[4]
+ packet_hash = de[6].get_hash()
+
+ serialised_entry = [
+ destination_hash,
+ timestamp,
+ received_from,
+ hops,
+ expires,
+ random_blobs,
+ interface_hash,
+ packet_hash
+ ]
+
+ serialised_paths.append(serialised_entry)
+
+ Transport.cache(de[6], force_cache=True)
+
+
+ serialised_tunnel = [tunnel_id, interface_hash, serialised_paths, expires]
+ serialised_tunnels.append(serialised_tunnel)
+
+ tunnels_path = RNS.Reticulum.storagepath+"/tunnels"
+ file = open(tunnels_path, "wb")
+ file.write(umsgpack.packb(serialised_tunnels))
+ file.close()
+
+ save_time = time.time() - save_start
+ if save_time < 1:
+ time_str = str(round(save_time*1000,2))+" ms"
+ else:
+ time_str = str(round(save_time,2))+" s"
+ RNS.log("Saved "+str(len(serialised_tunnels))+" tunnel table entries in "+time_str, RNS.LOG_DEBUG)
+ except Exception as e:
+ RNS.log("Could not save tunnel table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
+
+ Transport.saving_tunnel_table = False
+*/
+#endif
+}
+
+/*static*/ void Transport::persist_data() {
+ TRACE("Transport::persist_data()");
+ write_packet_hashlist();
+ write_path_table();
+ write_tunnel_table();
+}
+
+/*static*/ void Transport::clean_caches() {
+ TRACE("Transport::clean_caches()");
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ // CBA Remove cached packets no longer in path list
+ std::list<std::string> files = OS::list_directory(Reticulum::_cachepath);
+ for (auto& file : files) {
+ TRACE("Transport::clean_caches: Checking for use of cached packet " + file);
+ bool found = false;
+ for (auto& [destination_hash, destination_entry] : _destination_table) {
+ if (file.compare(destination_entry._announce_packet.toHex()) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ TRACE("Transport::clean_caches: No matching path found, removing cached packet " + file);
+ char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, file.c_str());
+ OS::remove_file(packet_cache_path);
+ }
+ }
+#endif
+}
+
+/*static*/ void Transport::dump_stats() {
+
+ OS::dump_heap_stats();
+
+ size_t memory = OS::heap_available();
+ size_t flash = OS::storage_available();
+
+ if (_last_memory == 0) {
+ _last_memory = memory;
+ }
+ if (_last_flash == 0) {
+ _last_flash = flash;
+ }
+
+ // memory
+ // storage
+ // _destinations
+ // _destination_table
+ // _reverse_table
+ // _announce_table
+ // _held_announces
+ HEADF(LOG_VERBOSE, "mem: %u (%u%%) [%d] flash: %u (%u%%) [%d] paths: %u dsts: %u revr: %u annc: %u held: %u", memory, (int)((double)memory / (double)OS::heap_size() * 100.0), memory - _last_memory, flash, (int)((double)flash / (double)OS::storage_size() * 100.0), flash - _last_flash, _destination_table.size(), _destinations.size(), _reverse_table.size(), _announce_table.size(), _held_announces.size());
+
+ // _path_requests
+ // _discovery_path_requests
+ // _pending_local_path_requests
+ // _discovery_pr_tags
+ // _control_destinations
+ // _control_hashes
+ VERBOSEF("preqs: %u dpreqs: %u ppreqs: %u dprt: %u cdsts: %u chshs: %u", _path_requests.size(), _discovery_path_requests.size(), _pending_local_path_requests.size(), _discovery_pr_tags.size(), _control_destinations.size(), _control_hashes.size());
+
+ // _packet_hashlist
+ // _receipts
+ // _link_table
+ // _pending_links
+ // _active_links
+ // _tunnels
+ uint32_t destination_path_responses = 0;
+ for (auto& [destination_hash, destination] : _destinations) {
+ destination_path_responses += destination.path_responses().size();
+ }
+ uint32_t interface_announces = 0;
+ for (auto& [interface_hash, interface] : _interfaces) {
+ interface_announces += interface.announce_queue().size();
+ }
+ VERBOSEF("phl: %u rcp: %u lt: %u pl: %u al: %u tun: %u", _packet_hashlist.size(), _receipts.size(), _link_table.size(), _pending_links.size(), _active_links.size(), _tunnels.size());
+ VERBOSEF("pin: %u pout: %u padd: %u dpr: %u ikd: %u ia: %u\r\n", _packets_received, _packets_sent, _destinations_added, destination_path_responses, Identity::_known_destinations.size(), interface_announces);
+
+ _last_memory = memory;
+ _last_flash = flash;
+
+}
+
+/*static*/ void Transport::exit_handler() {
+ TRACE("Transport::exit_handler()");
+ if (!_owner.is_connected_to_shared_instance()) {
+ persist_data();
+ }
+}
+
+/*static*/ Destination Transport::find_destination_from_hash(const Bytes& destination_hash) {
+ TRACE("Transport::find_destination_from_hash: Searching for destination " + destination_hash.toHex());
+#if defined(DESTINATIONS_SET)
+ for (const Destination& destination : _destinations) {
+ if (destination.get_hash() == destination_hash) {
+ TRACE("Transport::find_destination_from_hash: Found destination " + destination.toString());
+ return destination;
+ }
+ }
+#elif defined(DESTINATIONS_MAP)
+ auto iter = _destinations.find(destination_hash);
+ if (iter != _destinations.end()) {
+ TRACE("Transport::find_destination_from_hash: Found destination " + (*iter).second.toString());
+ return (*iter).second;
+ }
+#endif
+
+ return {Type::NONE};
+}
+
+/*static*/ void Transport::cull_path_table() {
+ TRACE("Transport::cull_path_table()");
+ if (_destination_table.size() > _path_table_maxsize) {
+ // TODO prune by age, or better yet by last use
+/*
+ std::map<Bytes, DestinationEntry>::iterator iter = _destination_table.begin();
+ // naively erase from front of table
+ std::advance(iter, _destination_table.size() - _path_table_maxsize + 1);
+ _destination_table.erase(_destination_table.begin(), iter);
+*/
+/*
+ uint16_t count = 0;
+ std::set<DestinationEntry> sorted_values;
+ MapToValues(_destination_table, sorted_values);
+ for (auto& destination_entry : sorted_values) {
+ Packet announce_packet = destination_entry.announce_packet();
+ TRACE("Transport::cull_path_table: Removing destination " + announce_packet.destination_hash().toHex() + " from path table");
+ // Remove destination from path table
+ if (_destination_table.erase(announce_packet.destination_hash()) < 1) {
+ WARNING("Failed to remove destination " + announce_packet.destination_hash().toHex() + " from path table");
+ }
+ // Remove announce packet from packet table
+ //if (_packet_table.erase(destination_entry._announce_packet) < 1) {
+ // WARNING("Failed to remove packet " + destination_entry._announce_packet.toHex() + " from packet table");
+ //}
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ // Remove cached packet file
+ char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, destination_entry._announce_packet.toHex().c_str());
+ if (OS::file_exists(packet_cache_path)) {
+ OS::remove_file(packet_cache_path);
+ }
+#endif
+ ++count;
+ if (_destination_table.size() <= _path_table_maxsize) {
+ break;
+ }
+ }
+ DEBUG("Removed " + std::to_string(count) + " path(s) from path table");
+*/
+ uint16_t count = 0;
+ std::vector<std::pair<Bytes,DestinationEntry>> sorted_pairs;
+ // Copy key/value pairs from map into vector
+ std::for_each(_destination_table.begin(), _destination_table.end(), [&](const std::pair<const Bytes, DestinationEntry>& ref) {
+ sorted_pairs.push_back(ref);
+ });
+ // Sort vector using specified comparator
+ std::sort(sorted_pairs.begin(), sorted_pairs.end(), [](const std::pair<Bytes,DestinationEntry> &left, const std::pair<Bytes,DestinationEntry> &right) {
+ return left.second._timestamp < right.second._timestamp;
+ });
+ // Iterate vector of sorted values
+ for (auto& [destination_hash, destination_entry] : sorted_pairs) {
+ TRACE("Transport::cull_path_table: Removing destination " + destination_hash.toHex() + " from path table");
+ // Remove destination from path table
+ if (_destination_table.erase(destination_hash) < 1) {
+ WARNING("Failed to remove destination " + destination_hash.toHex() + " from path table");
+ }
+ // Remove announce packet from packet table
+ //if (_packet_table.erase(destination_entry._announce_packet) < 1) {
+ // WARNING("Failed to remove packet " + destination_entry._announce_packet.toHex() + " from packet table");
+ //}
+#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
+ // Remove cached packet file
+ char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE];
+ snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, destination_entry._announce_packet.toHex().c_str());
+ if (OS::file_exists(packet_cache_path)) {
+ OS::remove_file(packet_cache_path);
+ }
+#endif
+ ++count;
+ if (_destination_table.size() <= _path_table_maxsize) {
+ break;
+ }
+ }
+ DEBUG("Removed " + std::to_string(count) + " path(s) from path table");
+ }
+}
+
+/*static*/ uint16_t Transport::remove_reverse_entries(const std::vector<Bytes>& hashes) {
+ uint16_t count = 0;
+ for (const auto& truncated_packet_hash : hashes) {
+ _reverse_table.erase(truncated_packet_hash);
+ ++count;
+ }
+ if (count > 0) {
+ TRACEF("Released %u reverse table entries", count);
+ }
+ return count;
+}
+
+/*static*/ uint16_t Transport::remove_links(const std::vector<Bytes>& hashes) {
+ uint16_t count = 0;
+ for (const auto& link_id : hashes) {
+ _link_table.erase(link_id);
+ ++count;
+ }
+ if (count > 0) {
+ TRACEF("Released %u links", count);
+ }
+ return count;
+}
+
+/*static*/ uint16_t Transport::remove_paths(const std::vector<Bytes>& hashes) {
+ uint16_t count = 0;
+ for (const auto& destination_hash : hashes) {
+ //_destination_table.erase(destination_hash);
+ remove_path(destination_hash);
+ ++count;
+ }
+ if (count > 0) {
+ TRACEF("Released %u paths", count);
+ }
+ return count;
+}
+
+/*static*/ uint16_t Transport::remove_discovery_path_requests(const std::vector<Bytes>& hashes) {
+ uint16_t count = 0;
+ for (const auto& destination_hash : hashes) {
+ _discovery_path_requests.erase(destination_hash);
+ ++count;
+ }
+ if (count > 0) {
+ TRACEF("Released %u waiting path requests", count);
+ }
+ return count;
+}
+
+/*static*/ uint16_t Transport::remove_tunnels(const std::vector<Bytes>& hashes) {
+ uint16_t count = 0;
+ for (const auto& tunnel_id : hashes) {
+ _tunnels.erase(tunnel_id);
+ ++count;
+ }
+ if (count > 0) {
+ TRACEF("Released %u tunnels", count);
+ }
+ return count;
+}

diff --git a/lib/microReticulum/src/Transport.h b/lib/microReticulum/src/Transport.h
new file mode 100755
index 0000000..5150cc6
--- /dev/null
+++ b/lib/microReticulum/src/Transport.h
@@ -0,0 +1,504 @@
+#pragma once
+
+#include "Packet.h"
+#include "Bytes.h"
+#include "Type.h"
+
+#include <map>
+#include <vector>
+#include <list>
+#include <set>
+#include <array>
+#include <memory>
+#include <functional>
+#include <stdint.h>
+
+//#define INTERFACES_SET
+//#define INTERFACES_LIST
+#define INTERFACES_MAP
+
+//#define DESTINATIONS_SET
+#define DESTINATIONS_MAP
+
+namespace RNS {
+
+ class Reticulum;
+ class Identity;
+ class Destination;
+ class Interface;
+ class Link;
+ class Packet;
+ class PacketReceipt;
+
+ class AnnounceHandler {
+ public:
+ // The initialisation method takes the optional
+ // aspect_filter argument. If aspect_filter is set to
+ // None, all announces will be passed to the instance.
+ // If only some announces are wanted, it can be set to
+ // an aspect string.
+ AnnounceHandler(const char* aspect_filter = nullptr) { if (aspect_filter != nullptr) _aspect_filter = aspect_filter; }
+ // This method will be called by Reticulums Transport
+ // system when an announce arrives that matches the
+ // configured aspect filter. Filters must be specific,
+ // and cannot use wildcards.
+ virtual void received_announce(const Bytes& destination_hash, const Identity& announced_identity, const Bytes& app_data) = 0;
+ std::string& aspect_filter() { return _aspect_filter; }
+ private:
+ std::string _aspect_filter;
+ };
+ using HAnnounceHandler = std::shared_ptr<AnnounceHandler>;
+
+ /*
+ Through static methods of this class you can interact with the
+ Transport system of Reticulum.
+ */
+ class Transport {
+
+ public:
+ class Callbacks {
+ public:
+ using receive_packet = void(*)(const Bytes& raw, const Interface& interface);
+ using transmit_packet = void(*)(const Bytes& raw, const Interface& interface);
+ using filter_packet = bool(*)(const Packet& packet);
+ public:
+ receive_packet _receive_packet = nullptr;
+ transmit_packet _transmit_packet = nullptr;
+ filter_packet _filter_packet = nullptr;
+ friend class Transport;
+ };
+
+ public:
+
+ class PacketEntry {
+ public:
+ PacketEntry() {}
+ PacketEntry(const Bytes& raw, double sent_at, const Bytes& destination_hash) :
+ _raw(raw),
+ _sent_at(sent_at),
+ _destination_hash(destination_hash)
+ {
+ }
+ PacketEntry(const Packet& packet) :
+ _raw(packet.raw()),
+ _sent_at(packet.sent_at()),
+ _destination_hash(packet.destination_hash())
+ {
+ }
+ public:
+ Bytes _raw;
+ double _sent_at = 0;
+ Bytes _destination_hash;
+ bool _cached = false;
+#ifndef NDEBUG
+ inline std::string debugString() const {
+ std::string dump;
+ dump = "PacketEntry: destination_hash=" + _destination_hash.toHex() +
+ " sent_at=" + std::to_string(_sent_at);
+ return dump;
+ }
+#endif
+ };
+
+ // CBA TODO Analyze safety of using Inrerface references here
+ // CBA TODO Analyze safety of using Packet references here
+ class DestinationEntry {
+ public:
+ DestinationEntry() {}
+ DestinationEntry(double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, const Bytes& receiving_interface, const Bytes& packet) :
+ _timestamp(timestamp),
+ _received_from(received_from),
+ _hops(announce_hops),
+ _expires(expires),
+ _random_blobs(random_blobs),
+ _receiving_interface(receiving_interface),
+ _announce_packet(packet)
+ {
+ }
+ public:
+ inline Interface receiving_interface() const { return find_interface_from_hash(_receiving_interface); }
+ inline Packet announce_packet() const { return get_cached_packet(_announce_packet); }
+ public:
+ double _timestamp = 0;
+ Bytes _received_from;
+ uint8_t _hops = 0;
+ double _expires = 0;
+ std::set<Bytes> _random_blobs;
+ //Interface _receiving_interface = {Type::NONE};
+ Bytes _receiving_interface;
+ //const Packet& _announce_packet;
+ //Packet _announce_packet = {Type::NONE};
+ Bytes _announce_packet;
+ inline bool operator < (const DestinationEntry& entry) const {
+ // sort by ascending timestamp (oldest entries at the top)
+ return _timestamp < entry._timestamp;
+ }
+#ifndef NDEBUG
+ inline std::string debugString() const {
+ std::string dump;
+ dump = "DestinationEntry: timestamp=" + std::to_string(_timestamp) +
+ " received_from=" + _received_from.toHex() +
+ " hops=" + std::to_string(_hops) +
+ " expires=" + std::to_string(_expires) +
+ //" random_blobs=" + _random_blobs +
+ " receiving_interface=" + _receiving_interface.toHex() +
+ " announce_packet=" + _announce_packet.toHex();
+ dump += " random_blobs=(";
+ for (auto& blob : _random_blobs) {
+ dump += blob.toHex() + ",";
+ }
+ dump += ")";
+ return dump;
+ }
+#endif
+ };
+
+ // CBA TODO Analyze safety of using Inrerface references here
+ // CBA TODO Analyze safety of using Packet references here
+ class AnnounceEntry {
+ public:
+ AnnounceEntry(double timestamp, double retransmit_timeout, uint8_t retries, const Bytes& received_from, uint8_t hops, const Packet& packet, uint8_t local_rebroadcasts, bool block_rebroadcasts, const Interface& attached_interface) :
+ _timestamp(timestamp),
+ _retransmit_timeout(retransmit_timeout),
+ _retries(retries),
+ _received_from(received_from),
+ _hops(hops),
+ _packet(packet),
+ _local_rebroadcasts(local_rebroadcasts),
+ _block_rebroadcasts(block_rebroadcasts),
+ _attached_interface(attached_interface)
+ {
+ }
+ public:
+ double _timestamp = 0;
+ double _retransmit_timeout = 0;
+ uint8_t _retries = 0;
+ const Bytes _received_from;
+ uint8_t _hops = 0;
+ // CBA Storing packet reference causes memory issues, presumably because orignal packet is being destroyed
+ // MUST use instance instad of reference!!!
+ //const Packet& _packet;
+ const Packet _packet = {Type::NONE};
+ uint8_t _local_rebroadcasts = 0;
+ bool _block_rebroadcasts = false;
+ const Interface _attached_interface = {Type::NONE};
+ };
+
+ // CBA TODO Analyze safety of using Inrerface references here
+ class LinkEntry {
+ public:
+ LinkEntry(double timestamp, const Bytes& next_hop, const Interface& outbound_interface, uint8_t remaining_hops, const Interface& receiving_interface, uint8_t hops, const Bytes& destination_hash, bool validated, double proof_timeout) :
+ _timestamp(timestamp),
+ _next_hop(next_hop),
+ _outbound_interface(outbound_interface),
+ _remaining_hops(remaining_hops),
+ _receiving_interface(receiving_interface),
+ _hops(hops),
+ _destination_hash(destination_hash),
+ _validated(validated),
+ _proof_timeout(proof_timeout)
+ {
+ }
+ public:
+ double _timestamp = 0;
+ const Bytes _next_hop;
+ const Interface _outbound_interface = {Type::NONE};
+ uint8_t _remaining_hops = 0;
+ Interface _receiving_interface = {Type::NONE};
+ uint8_t _hops = 0;
+ const Bytes _destination_hash;
+ bool _validated = false;
+ double _proof_timeout = 0;
+ };
+
+ // CBA TODO Analyze safety of using Inrerface references here
+ class ReverseEntry {
+ public:
+ ReverseEntry(const Interface& receiving_interface, const Interface& outbound_interface, double timestamp) :
+ _receiving_interface(receiving_interface),
+ _outbound_interface(outbound_interface),
+ _timestamp(timestamp)
+ {
+ }
+ public:
+ Interface _receiving_interface = {Type::NONE};
+ const Interface _outbound_interface = {Type::NONE};
+ double _timestamp = 0;
+ };
+
+ // CBA TODO Analyze safety of using Inrerface references here
+ class PathRequestEntry {
+ public:
+ PathRequestEntry(const Bytes& destination_hash, double timeout, const Interface& requesting_interface) :
+ _destination_hash(destination_hash),
+ _timeout(timeout),
+ _requesting_interface(requesting_interface)
+ {
+ }
+ public:
+ const Bytes _destination_hash;
+ double _timeout = 0;
+ const Interface _requesting_interface = {Type::NONE};
+ };
+
+/*
+ // CBA TODO Analyze safety of using Inrerface references here
+ class SerialisedEntry {
+ public:
+ SerialisedEntry(const Bytes& destination_hash, double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, Interface& receiving_interface, const Packet& packet) :
+ _destination_hash(destination_hash),
+ _timestamp(timestamp),
+ _hops(announce_hops),
+ _expires(expires),
+ _random_blobs(random_blobs),
+ _receiving_interface(receiving_interface),
+ _announce_packet(packet)
+ {
+ }
+ public:
+ const Bytes _destination_hash;
+ double _timestamp = 0;
+ const Bytes _received_from;
+ uint8_t _hops = 0;
+ double _expires = 0;
+ std::set<Bytes> _random_blobs;
+ Interface _receiving_interface = {Type::NONE};
+ Packet _announce_packet = {Type::NONE};
+ };
+*/
+
+ // CBA TODO Analyze safety of using Inrerface references here
+ class TunnelEntry {
+ public:
+ TunnelEntry(const Bytes& tunnel_id, const Bytes& interface_hash, double expires) :
+ _tunnel_id(tunnel_id),
+ _interface_hash(interface_hash),
+ _expires(expires)
+ {
+ }
+ public:
+ const Bytes _tunnel_id;
+ const Bytes _interface_hash;
+ std::map<Bytes, DestinationEntry> _serialised_paths;
+ double _expires = 0;
+ };
+
+ class RateEntry {
+ public:
+ RateEntry(double now) :
+ _last(now)
+ {
+ _timestamps.push_back(now);
+ }
+ public:
+ double _last = 0.0;
+ double _rate_violations = 0.0;
+ double _blocked_until = 0.0;
+ std::vector<double> _timestamps;
+ };
+
+ public:
+ static void start(const Reticulum& reticulum_instance);
+ static void loop();
+ static void jobs();
+ static void transmit(Interface& interface, const Bytes& raw);
+ static bool outbound(Packet& packet);
+ static bool packet_filter(const Packet& packet);
+ //static void inbound(const Bytes& raw, const Interface& interface = {Type::NONE});
+ static void inbound(const Bytes& raw, const Interface& interface);
+ static void inbound(const Bytes& raw);
+ static void synthesize_tunnel(const Interface& interface);
+ static void tunnel_synthesize_handler(const Bytes& data, const Packet& packet);
+ static void handle_tunnel(const Bytes& tunnel_id, const Interface& interface);
+ static void register_interface(Interface& interface);
+ static void deregister_interface(const Interface& interface);
+ inline static const std::map<Bytes, Interface&> get_interfaces() { return _interfaces; }
+ static void register_destination(Destination& destination);
+ static void deregister_destination(const Destination& destination);
+ static void register_link(Link& link);
+ static void activate_link(Link& link);
+ static void register_announce_handler(HAnnounceHandler handler);
+ static void deregister_announce_handler(HAnnounceHandler handler);
+ static Interface find_interface_from_hash(const Bytes& interface_hash);
+ static bool should_cache_packet(const Packet& packet);
+ static bool cache_packet(const Packet& packet, bool force_cache = false);
+ static Packet get_cached_packet(const Bytes& packet_hash);
+ static bool clear_cached_packet(const Bytes& packet_hash);
+ static bool cache_request_packet(const Packet& packet);
+ static void cache_request(const Bytes& packet_hash, const Destination& destination);
+ static bool remove_path(const Bytes& destination_hash);
+ static bool has_path(const Bytes& destination_hash);
+ static uint8_t hops_to(const Bytes& destination_hash);
+ static Bytes next_hop(const Bytes& destination_hash);
+ static Interface next_hop_interface(const Bytes& destination_hash);
+ static uint32_t next_hop_interface_bitrate(const Bytes& destination_hash);
+ static uint16_t next_hop_interface_hw_mtu(const Bytes& destination_hash);
+ static double next_hop_per_bit_latency(const Bytes& destination_hash);
+ static double next_hop_per_byte_latency(const Bytes& destination_hash);
+ static double first_hop_timeout(const Bytes& destination_hash);
+ static double extra_link_proof_timeout(const Interface& interface);
+ static bool expire_path(const Bytes& destination_hash);
+ //static void request_path(const Bytes& destination_hash, const Interface& on_interface = {Type::NONE}, const Bytes& tag = {}, bool recursive = false);
+ static void request_path(const Bytes& destination_hash, const Interface& on_interface, const Bytes& tag = {}, bool recursive = false);
+ static void request_path(const Bytes& destination_hash);
+ static void path_request_handler(const Bytes& data, const Packet& packet);
+ static void path_request(const Bytes& destination_hash, bool is_from_local_client, const Interface& attached_interface, const Bytes& requestor_transport_id = {}, const Bytes& tag = {});
+ static bool from_local_client(const Packet& packet);
+ static bool is_local_client_interface(const Interface& interface);
+ static bool interface_to_shared_instance(const Interface& interface);
+ static void detach_interfaces();
+ static void shared_connection_disappeared();
+ static void shared_connection_reappeared();
+ static void drop_announce_queues();
+ static uint64_t announce_emitted(const Packet& packet);
+ static void write_packet_hashlist();
+ static bool read_path_table();
+ static bool write_path_table();
+ static void read_tunnel_table();
+ static void write_tunnel_table();
+ static void persist_data();
+ static void clean_caches();
+ static void dump_stats();
+ static void exit_handler();
+
+ static uint16_t remove_reverse_entries(const std::vector<Bytes>& hashes);
+ static uint16_t remove_links(const std::vector<Bytes>& hashes);
+ static uint16_t remove_paths(const std::vector<Bytes>& hashes);
+ static uint16_t remove_discovery_path_requests(const std::vector<Bytes>& hashes);
+ static uint16_t remove_tunnels(const std::vector<Bytes>& hashes);
+
+ static Destination find_destination_from_hash(const Bytes& destination_hash);
+
+ // CBA
+ static void cull_path_table();
+
+ // getters/setters
+ static inline void set_receive_packet_callback(Callbacks::receive_packet callback) { _callbacks._receive_packet = callback; }
+ static inline void set_transmit_packet_callback(Callbacks::transmit_packet callback) { _callbacks._transmit_packet = callback; }
+ static inline void set_filter_packet_callback(Callbacks::filter_packet callback) { _callbacks._filter_packet = callback; }
+ static inline const Reticulum& reticulum() { return _owner; }
+ static inline const Identity& identity() { return _identity; }
+ inline static uint16_t path_table_maxsize() { return _path_table_maxsize; }
+ inline static void path_table_maxsize(uint16_t path_table_maxsize) { _path_table_maxsize = path_table_maxsize; }
+ inline static uint16_t probe_destination_enabled() { return _path_table_maxpersist; }
+ inline static void path_table_maxpersist(uint16_t path_table_maxpersist) { _path_table_maxpersist = path_table_maxpersist; }
+ // CBA TEST
+ static inline void identity(Identity& identity) { _identity = identity; }
+
+ inline static const std::map<Bytes, DestinationEntry>& get_destination_table() { return _destination_table; }
+ inline static const std::map<Bytes, RateEntry>& get_announce_rate_table() { return _announce_rate_table; }
+ inline static const std::map<Bytes, LinkEntry>& get_link_table() { return _link_table; }
+
+ private:
+ // CBA MUST use references to interfaces here in order for virtul overrides for send/receive to work
+#if defined(INTERFACES_SET)
+ // set sorted, can use find
+ //static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _interfaces; // All active interfaces
+ static std::set<std::reference_wrapper<Interface>, std::less<Interface>> _interfaces; // All active interfaces
+#elif defined(INTERFACES_LIST)
+ // list is unsorted, can't use find
+ static std::list<std::reference_wrapper<Interface>> _interfaces; // All active interfaces
+#elif defined(INTERFACES_MAP)
+ // map is sorted, can use find
+ static std::map<Bytes, Interface&> _interfaces; // All active interfaces
+#endif
+#if defined(DESTINATIONS_SET)
+ static std::set<Destination> _destinations; // All active destinations
+#elif defined(DESTINATIONS_MAP)
+ static std::map<Bytes, Destination> _destinations; // All active destinations
+#endif
+ // CBA TODO: Reconsider using std::set for enforcing uniqueness. Maybe consider std::map keyed on hash instead
+ static std::set<Link> _pending_links; // Links that are being established
+ static std::set<Link> _active_links; // Links that are active
+ static std::set<Bytes> _packet_hashlist; // A list of packet hashes for duplicate detection
+ static std::list<PacketReceipt> _receipts; // Receipts of all outgoing packets for proof processing
+
+ // TODO: "destination_table" should really be renamed to "path_table"
+ // Notes on memory usage: 1 megabyte of memory can store approximately
+ // 55.100 path table entries or approximately 22.300 link table entries.
+
+ static std::map<Bytes, AnnounceEntry> _announce_table; // A table for storing announces currently waiting to be retransmitted
+ static std::map<Bytes, DestinationEntry> _destination_table; // A lookup table containing the next hop to a given destination
+ static std::map<Bytes, ReverseEntry> _reverse_table; // A lookup table for storing packet hashes used to return proofs and replies
+ static std::map<Bytes, LinkEntry> _link_table; // A lookup table containing hops for links
+ static std::map<Bytes, AnnounceEntry> _held_announces; // A table containing temporarily held announce-table entries
+ static std::set<HAnnounceHandler> _announce_handlers; // A table storing externally registered announce handlers
+ static std::map<Bytes, TunnelEntry> _tunnels; // A table storing tunnels to other transport instances
+ static std::map<Bytes, RateEntry> _announce_rate_table; // A table for keeping track of announce rates
+ static std::map<Bytes, double> _path_requests; // A table for storing path request timestamps
+
+ static std::map<Bytes, PathRequestEntry> _discovery_path_requests; // A table for keeping track of path requests on behalf of other nodes
+ static std::set<Bytes> _discovery_pr_tags; // A table for keeping track of tagged path requests
+
+ // Transport control destinations are used
+ // for control purposes like path requests
+ static std::set<Destination> _control_destinations;
+ static std::set<Bytes> _control_hashes;
+
+ // Interfaces for communicating with
+ // local clients connected to a shared
+ // Reticulum instance
+ //static std::set<Interface> _local_client_interfaces;
+ static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _local_client_interfaces;
+
+ static std::map<Bytes, const Interface&> _pending_local_path_requests;
+
+ // CBA
+ static std::map<Bytes, PacketEntry> _packet_table; // A lookup table containing announce packets for known paths
+
+ //z _local_client_rssi_cache = []
+ //z _local_client_snr_cache = []
+ static uint16_t _LOCAL_CLIENT_CACHE_MAXSIZE;
+
+ static double _start_time;
+ static bool _jobs_locked;
+ static bool _jobs_running;
+ static float _job_interval;
+ static double _jobs_last_run;
+ static double _links_last_checked;
+ static float _links_check_interval;
+ static double _receipts_last_checked;
+ static float _receipts_check_interval;
+ static double _announces_last_checked;
+ static float _announces_check_interval;
+ static double _tables_last_culled;
+ static float _tables_cull_interval;
+ static bool _saving_path_table;
+ static uint16_t _hashlist_maxsize;
+ static uint16_t _max_pr_tags;
+
+ // CBA
+ static uint16_t _path_table_maxsize;
+ static uint16_t _path_table_maxpersist;
+ static double _last_saved;
+ static float _save_interval;
+ static uint32_t _destination_table_crc;
+
+ static Reticulum _owner;
+ static Identity _identity;
+
+ // CBA
+ static Callbacks _callbacks;
+
+ // CBA Stats
+ static uint32_t _packets_sent;
+ static uint32_t _packets_received;
+ static uint32_t _destinations_added;
+ static size_t _last_memory;
+ static size_t _last_flash;
+ };
+
+ template <typename M, typename S>
+ void MapToValues(const M& m, S& s) {
+ for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
+ s.insert(it->second);
+ }
+ }
+
+ template <typename M, typename S>
+ void MapToPairs(const M& m, S& s) {
+ for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
+ s.push_back(*it);
+ }
+ }
+}

diff --git a/lib/microReticulum/src/Type.h b/lib/microReticulum/src/Type.h
new file mode 100755
index 0000000..4fbf066
--- /dev/null
+++ b/lib/microReticulum/src/Type.h
@@ -0,0 +1,549 @@
+#pragma once
+
+#include "Log.h"
+
+#include <stdint.h>
+#include <math.h>
+
+
+namespace RNS { namespace Type {
+
+ // generic empty object constructor type
+ enum NoneConstructor {
+ NONE
+ };
+
+ namespace Persistence {
+ //static const uint16_t DOCUMENT_MAXSIZE = 1024;
+ static const uint16_t DOCUMENT_MAXSIZE = 8192;
+ //static const uint16_t DOCUMENT_MAXSIZE = 16384;
+ static const uint16_t BUFFER_MAXSIZE = Persistence::DOCUMENT_MAXSIZE * 1.5; // Json write buffer of 1.5 times document seems to be sufficient
+ }
+
+ namespace Cryptography {
+ namespace Fernet {
+ static const uint8_t FERNET_OVERHEAD = 48; // Bytes
+ }
+ namespace Token {
+ static const uint8_t TOKEN_OVERHEAD = 48; // Bytes
+ enum token_mode {
+ MODE_AES = 0x01,
+ MODE_AES_128_CBC = 0x02,
+ MODE_AES_256_CBC = 0x03,
+ };
+ }
+ }
+
+ namespace Reticulum {
+
+ /*
+ The MTU that Reticulum adheres to, and will expect other peers to
+ adhere to. By default, the MTU is 507 bytes. In custom RNS network
+ implementations, it is possible to change this value, but doing so will
+ completely break compatibility with all other RNS networks. An identical
+ MTU is a prerequisite for peers to communicate in the same network.
+
+ Unless you really know what you are doing, the MTU should be left at
+ the default value.
+
+ Future minimum will probably be locked in at 251 bytes to support
+ networks with segments of different MTUs. Absolute minimum is 219.
+ */
+ static const uint16_t MTU = 500;
+
+ /*
+ Whether automatic link MTU discovery is enabled by default in this
+ release. Link MTU discovery significantly increases throughput over
+ fast links, but requires all intermediary hops to also support it.
+ Support for this feature was added in RNS version 0.9.0. This option
+ will become enabled by default in the near future. Please update your
+ RNS instances.
+ */
+ static const bool LINK_MTU_DISCOVERY = true;
+
+ static const uint16_t MAX_QUEUED_ANNOUNCES = 16384;
+ static const uint32_t QUEUED_ANNOUNCE_LIFE = 60*60*24;
+
+ static const uint8_t ANNOUNCE_CAP = 2;
+ /*
+ The maximum percentage of interface bandwidth that, at any given time,
+ may be used to propagate announces. If an announce was scheduled for
+ broadcasting on an interface, but doing so would exceed the allowed
+ bandwidth allocation, the announce will be queued for transmission
+ when there is bandwidth available.
+
+ Reticulum will always prioritise propagating announces with fewer
+ hops, ensuring that distant, large networks with many peers on fast
+ links don't overwhelm the capacity of smaller networks on slower
+ mediums. If an announce remains queued for an extended amount of time,
+ it will eventually be dropped.
+
+ This value will be applied by default to all created interfaces,
+ but it can be configured individually on a per-interface basis.
+ */
+
+ static const uint16_t MINIMUM_BITRATE = 500;
+
+ // TODO: To reach the 300bps level without unreasonably impacting
+ // performance on faster links, we need a mechanism for setting
+ // this value more intelligently. One option could be inferring it
+ // from interface speed, but a better general approach would most
+ // probably be to let Reticulum somehow continously build a map of
+ // per-hop latencies and use this map for the timeout calculation.
+ static const uint8_t DEFAULT_PER_HOP_TIMEOUT = 6;
+
+ static const uint16_t HASHLENGTH = 256; // In bits
+ // Length of truncated hashes in bits.
+ static const uint16_t TRUNCATED_HASHLENGTH = 128; // In bits
+
+ static const uint16_t HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH/8)*1; // In bytes
+ static const uint16_t HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH/8)*2; // In bytes
+ static const uint16_t IFAC_MIN_SIZE = 1;
+ //z IFAC_SALT = bytes.fromhex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8")
+
+ static const uint16_t MDU = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE;
+
+ static const uint32_t RESOURCE_CACHE = 60*60*24;
+ // CBA TEST
+ //static const uint16_t JOB_INTERVAL = 60*5;
+ static const uint16_t JOB_INTERVAL = 60;
+ // CBA TEST
+ //static const uint16_t CLEAN_INTERVAL = 60*15;
+ static const uint16_t CLEAN_INTERVAL = 60;
+ // CBA MCU
+ //static const uint16_t PERSIST_INTERVAL = 60*60*12;
+ // CBA TEST
+ //static const uint16_t PERSIST_INTERVAL = 60*60;
+ static const uint16_t PERSIST_INTERVAL = 60;
+ static const uint16_t GRACIOUS_PERSIST_INTERVAL = 60*5;
+
+ static const uint8_t DESTINATION_LENGTH = TRUNCATED_HASHLENGTH/8; // In bytes
+
+ // CBA MCU
+ //static const uint8_t FILEPATH_MAXSIZE = 64;
+ static const uint8_t FILEPATH_MAXSIZE = 96;
+
+ }
+
+ namespace Identity {
+
+ // The curve used for Elliptic Curve DH key exchanges
+ //static const char CURVE[] = "Curve25519";
+ static constexpr const char* CURVE = "Curve25519";
+
+ // X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
+ static const uint16_t KEYSIZE = 256*2;
+
+ // X.25519 ratchet key size in bits.
+ static const uint16_t RATCHETSIZE = 256;
+
+ /*
+ The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
+ announced ratchet, and remember it for up to ``RATCHET_EXPIRY`` since receiving it, after which it will be discarded.
+ If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.
+ */
+ static const uint32_t RATCHET_EXPIRY = 60*60*24*30;
+
+ // Non-configurable constants
+ static const uint8_t FERNET_OVERHEAD = Cryptography::Fernet::FERNET_OVERHEAD;
+ static const uint8_t TOKEN_OVERHEAD = Cryptography::Token::TOKEN_OVERHEAD;
+ static const uint8_t AES128_BLOCKSIZE = 16; // In bytes
+ static const uint16_t HASHLENGTH = Reticulum::HASHLENGTH; // In bits
+ static const uint16_t SIGLENGTH = KEYSIZE; // In bits
+
+ static const uint8_t NAME_HASH_LENGTH = 80;
+ static const uint8_t RANDOM_HASH_LENGTH = 80;
+ // Constant specifying the truncated hash length (in bits) used by Reticulum
+ // for addressable hashes and other purposes. Non-configurable.
+ static const uint16_t TRUNCATED_HASHLENGTH = Reticulum::TRUNCATED_HASHLENGTH; // In bits
+
+ static const uint16_t DERIVED_KEY_LENGTH = 512/8;
+ static const uint16_t DERIVED_KEY_LENGTH_LEGACY = 256/8;
+
+ }
+
+ namespace Destination {
+
+ enum types {
+ SINGLE = 0x00,
+ GROUP = 0x01,
+ PLAIN = 0x02,
+ LINK = 0x03,
+ };
+
+ enum proof_strategies {
+ PROVE_NONE = 0x21,
+ PROVE_APP = 0x22,
+ PROVE_ALL = 0x23,
+ };
+
+ enum request_policies {
+ ALLOW_NONE = 0x00,
+ ALLOW_ALL = 0x01,
+ ALLOW_LIST = 0x02,
+ };
+
+ enum directions {
+ IN = 0x11,
+ OUT = 0x12,
+ };
+
+ const uint8_t PR_TAG_WINDOW = 30;
+
+ }
+
+ namespace Link {
+
+ // The curve used for Elliptic Curve DH key exchanges
+ static constexpr const char* CURVE = Identity::CURVE;
+
+ static const uint16_t ECPUBSIZE = 32+32;
+ static const uint8_t KEYSIZE = 32;
+
+ //static const uint16_t MDU = floor((Reticulum::MTU-Reticulum::IFAC_MIN_SIZE-Reticulum::HEADER_MINSIZE-Identity::FERNET_OVERHEAD)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
+ static const uint16_t MDU = ((Reticulum::MTU-Reticulum::IFAC_MIN_SIZE-Reticulum::HEADER_MINSIZE-Identity::FERNET_OVERHEAD)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
+
+ // Timeout for link establishment in seconds per hop to destination.
+ static const uint8_t ESTABLISHMENT_TIMEOUT_PER_HOP = Reticulum::DEFAULT_PER_HOP_TIMEOUT;
+
+ static const uint8_t LINK_MTU_SIZE = 3;
+ static const uint8_t TRAFFIC_TIMEOUT_MIN_MS = 5;
+ // RTT timeout factor used in link timeout calculation.
+ static const uint8_t TRAFFIC_TIMEOUT_FACTOR = 6;
+ static const float KEEPALIVE_MAX_RTT = 1.75;
+ static const uint8_t KEEPALIVE_TIMEOUT_FACTOR = 4;
+ // Grace period in seconds used in link timeout calculation.
+ static const uint8_t STALE_GRACE = 2;
+ // Interval for sending keep-alive packets on established links in seconds.
+ static const uint16_t KEEPALIVE = 360;
+ /*
+ If no traffic or keep-alive packets are received within this period, the
+ link will be marked as stale, and a final keep-alive packet will be sent.
+ If after this no traffic or keep-alive packets are received within ``RTT`` *
+ ``KEEPALIVE_TIMEOUT_FACTOR`` + ``STALE_GRACE``, the link is considered timed out,
+ and will be torn down.
+ */
+ static const uint8_t STALE_FACTOR = 2;
+ static const uint16_t STALE_TIME = 2*KEEPALIVE;
+
+ static const uint8_t WATCHDOG_MAX_SLEEP = 5;
+
+ static const uint64_t MTU_BYTEMASK = 0x1FFFFF;
+ static const uint8_t MODE_BYTEMASK = 0xE0;
+
+ enum status {
+ PENDING = 0x00,
+ HANDSHAKE = 0x01,
+ ACTIVE = 0x02,
+ STALE = 0x03,
+ CLOSED = 0x04
+ };
+
+ enum teardown_reason {
+ TEARDOWN_NONE = 0x00,
+ TIMEOUT = 0x01,
+ INITIATOR_CLOSED = 0x02,
+ DESTINATION_CLOSED = 0x03,
+ };
+
+ enum resource_strategy {
+ ACCEPT_NONE = 0x00,
+ ACCEPT_APP = 0x01,
+ ACCEPT_ALL = 0x02,
+ };
+
+ enum link_mode {
+ MODE_AES128_CBC = 0x00,
+ MODE_AES256_CBC = 0x01,
+ MODE_AES256_GCM = 0x02,
+ MODE_OTP_RESERVED = 0x03,
+ MODE_PQ_RESERVED_1 = 0x04,
+ MODE_PQ_RESERVED_2 = 0x05,
+ MODE_PQ_RESERVED_3 = 0x06,
+ MODE_PQ_RESERVED_4 = 0x07,
+ };
+
+/*
+ MODE_DESCRIPTIONS = {MODE_AES128_CBC: "AES_128_CBC",
+ MODE_AES256_CBC: "AES_256_CBC",
+ MODE_AES256_GCM: "MODE_AES256_GCM",
+ MODE_OTP_RESERVED: "MODE_OTP_RESERVED",
+ MODE_PQ_RESERVED_1: "MODE_PQ_RESERVED_1",
+ MODE_PQ_RESERVED_2: "MODE_PQ_RESERVED_2",
+ MODE_PQ_RESERVED_3: "MODE_PQ_RESERVED_3",
+ MODE_PQ_RESERVED_4: "MODE_PQ_RESERVED_4"}
+*/
+
+ }
+
+ namespace RequestReceipt {
+
+ enum status {
+ FAILED = 0x00,
+ SENT = 0x01,
+ DELIVERED = 0x02,
+ RECEIVING = 0x03,
+ READY = 0x04,
+ };
+
+ }
+
+ namespace Interface {
+
+ // Interface mode definitions
+ enum modes {
+ MODE_NONE = 0x00,
+ MODE_FULL = 0x01,
+ MODE_POINT_TO_POINT = 0x04,
+ MODE_ACCESS_POINT = 0x08,
+ MODE_ROAMING = 0x10,
+ MODE_BOUNDARY = 0x20,
+ MODE_GATEWAY = 0x40,
+ };
+
+ }
+
+ namespace Packet {
+
+ // Packet types
+ enum types {
+ DATA = 0x00, // Data packets
+ ANNOUNCE = 0x01, // Announces
+ LINKREQUEST = 0x02, // Link requests
+ PROOF = 0x03, // Proofs
+ };
+
+ // Header types
+ enum header_types {
+ HEADER_1 = 0x00, // Normal header format
+ HEADER_2 = 0x01, // Header format used for packets in transport
+ };
+
+ // Packet context types
+ enum context_types {
+ CONTEXT_NONE = 0x00, // Generic data packet
+ RESOURCE = 0x01, // Packet is part of a resource
+ RESOURCE_ADV = 0x02, // Packet is a resource advertisement
+ RESOURCE_REQ = 0x03, // Packet is a resource part request
+ RESOURCE_HMU = 0x04, // Packet is a resource hashmap update
+ RESOURCE_PRF = 0x05, // Packet is a resource proof
+ RESOURCE_ICL = 0x06, // Packet is a resource initiator cancel message
+ RESOURCE_RCL = 0x07, // Packet is a resource receiver cancel message
+ CACHE_REQUEST = 0x08, // Packet is a cache request
+ REQUEST = 0x09, // Packet is a request
+ RESPONSE = 0x0A, // Packet is a response to a request
+ PATH_RESPONSE = 0x0B, // Packet is a response to a path request
+ COMMAND = 0x0C, // Packet is a command
+ COMMAND_STATUS = 0x0D, // Packet is a status of an executed command
+ CHANNEL = 0x0E, // Packet contains link channel data
+ KEEPALIVE = 0xFA, // Packet is a keepalive packet
+ LINKIDENTIFY = 0xFB, // Packet is a link peer identification proof
+ LINKCLOSE = 0xFC, // Packet is a link close message
+ LINKPROOF = 0xFD, // Packet is a link packet proof
+ LRRTT = 0xFE, // Packet is a link request round-trip time measurement
+ LRPROOF = 0xFF, // Packet is a link request proof
+ };
+
+ // Context flag values
+ enum context_flags {
+ FLAG_SET = 0x01,
+ FLAG_UNSET = 0x00,
+ };
+
+ // This is used to calculate allowable
+ // payload sizes
+ static const uint16_t HEADER_MAXSIZE = Reticulum::HEADER_MAXSIZE;
+ static const uint16_t MDU = Reticulum::MDU;
+
+ // With an MTU of 500, the maximum of data we can
+ // send in a single encrypted packet is given by
+ // the below calculation; 383 bytes.
+ //static const uint16_t ENCRYPTED_MDU = floor((Reticulum::MDU-Identity::FERNET_OVERHEAD-Identity::KEYSIZE/16)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
+ //static const uint16_t ENCRYPTED_MDU;
+ static const uint16_t ENCRYPTED_MDU = ((Reticulum::MDU-Identity::FERNET_OVERHEAD-Identity::KEYSIZE/16)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
+ // The maximum size of the payload data in a single encrypted packet
+ static const uint16_t PLAIN_MDU = MDU;
+ // The maximum size of the payload data in a single unencrypted packet
+
+ static const uint8_t TIMEOUT_PER_HOP = Reticulum::DEFAULT_PER_HOP_TIMEOUT;
+
+ }
+
+ namespace PacketReceipt {
+
+ // Receipt status constants
+ enum Status {
+ FAILED = 0x00,
+ SENT = 0x01,
+ DELIVERED = 0x02,
+ CULLED = 0xFF
+ };
+
+ static const uint16_t EXPL_LENGTH = Identity::HASHLENGTH / 8 + Identity::SIGLENGTH / 8;
+ static const uint16_t IMPL_LENGTH = Identity::SIGLENGTH / 8;
+
+ }
+
+ namespace Transport {
+
+ enum types {
+ BROADCAST = 0x00,
+ TRANSPORT = 0x01,
+ RELAY = 0x02,
+ TUNNEL = 0x03,
+ NONE = 0xFF,
+ };
+
+ enum reachabilities {
+ REACHABILITY_UNREACHABLE = 0x00,
+ REACHABILITY_DIRECT = 0x01,
+ REACHABILITY_TRANSPORT = 0x02,
+ };
+
+ enum state {
+ STATE_UNKNOWN = 0x00,
+ STATE_UNRESPONSIVE = 0x01,
+ STATE_RESPONSIVE = 0x02,
+ };
+
+ static constexpr const char* APP_NAME = "rnstransport";
+
+ // Maximum amount of hops that Reticulum will transport a packet.
+ static const uint8_t PATHFINDER_M = 128; // Max hops
+
+ static const uint8_t PATHFINDER_R = 1; // Retransmit retries
+ static const uint8_t PATHFINDER_G = 5; // Retry grace period
+ static constexpr const float PATHFINDER_RW = 0.5; // Random window for announce rebroadcast
+
+ // TODO: Calculate an optimal number for this in
+ // various situations
+ static const uint8_t LOCAL_REBROADCASTS_MAX = 2; // How many local rebroadcasts of an announce is allowed
+
+ static const uint8_t PATH_REQUEST_TIMEOUT = 15; // Default timuout for client path requests in seconds
+ static constexpr const float PATH_REQUEST_GRACE = 0.35; // Grace time before a path announcement is made, allows directly reachable peers to respond first
+ static const uint8_t PATH_REQUEST_RW = 2; // Path request random window
+ static const uint8_t PATH_REQUEST_MI = 5; // Minimum interval in seconds for automated path requests
+
+ static constexpr const float LINK_TIMEOUT = Link::STALE_TIME * 1.25;
+ static const uint16_t REVERSE_TIMEOUT = 30*60; // Reverse table entries are removed after 30 minutes
+ // CBA MCU
+ //static const uint16_t MAX_RECEIPTS = 1024; // Maximum number of receipts to keep track of
+ static const uint16_t MAX_RECEIPTS = 20; // Maximum number of receipts to keep track of
+ static const uint8_t MAX_RATE_TIMESTAMPS = 16; // Maximum number of announce timestamps to keep per destination
+ static const uint8_t PERSIST_RANDOM_BLOBS = 32; // Maximum number of random blobs per destination to persist to disk
+ static const uint8_t MAX_RANDOM_BLOBS = 64; // Maximum number of random blobs per destination to keep in memory
+
+ // CBA MCU
+ //static const uint32_t DESTINATION_TIMEOUT = 60*60*24*7; // Destination table entries are removed if unused for one week
+ //static const uint32_t PATHFINDER_E = 60*60*24*7; // Path expiration of one week
+ //static const uint32_t AP_PATH_TIME = 60*60*24; // Path expiration of one day for Access Point paths
+ //static const uint32_t ROAMING_PATH_TIME = 60*60*6; // Path expiration of 6 hours for Roaming paths
+ static const uint32_t DESTINATION_TIMEOUT = 60*60*24*1; // Destination table entries are removed if unused for one day
+ static const uint32_t PATHFINDER_E = 60*60*24*1; // Path expiration of one day
+ static const uint32_t AP_PATH_TIME = 60*60*6; // Path expiration of 6 hours for Access Point paths
+ static const uint32_t ROAMING_PATH_TIME = 60*60*1; // Path expiration of 1 hour for Roaming paths
+
+ static const uint16_t LOCAL_CLIENT_CACHE_MAXSIZE = 512;
+ }
+
+ namespace Resource {
+
+ // The initial window size at beginning of transfer
+ static const uint8_t WINDOW = 4;
+
+ // Absolute minimum window size during transfer
+ static const uint8_t WINDOW_MIN = 1;
+
+ // The maximum window size for transfers on slow links
+ static const uint8_t WINDOW_MAX_SLOW = 10;
+
+ // The maximum window size for transfers on fast links
+ static const uint8_t WINDOW_MAX_FAST = 75;
+
+ // For calculating maps and guard segments, this
+ // must be set to the global maximum window.
+ static const uint8_t WINDOW_MAX = WINDOW_MAX_FAST;
+
+ // If the fast rate is sustained for this many request
+ // rounds, the fast link window size will be allowed.
+ static const uint8_t FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2;
+
+ // If the RTT rate is higher than this value,
+ // the max window size for fast links will be used.
+ // The default is 50 Kbps (the value is stored in
+ // bytes per second, hence the "/ 8").
+ static const uint16_t RATE_FAST = (50*1000) / 8;
+
+ // The minimum allowed flexibility of the window size.
+ // The difference between window_max and window_min
+ // will never be smaller than this value.
+ static const uint8_t WINDOW_FLEXIBILITY = 4;
+
+ // Number of bytes in a map hash
+ static const uint8_t MAPHASH_LEN = 4;
+ static const uint16_t SDU = Packet::MDU;
+ static const uint8_t RANDOM_HASH_SIZE = 4;
+
+ // This is an indication of what the
+ // maximum size a resource should be, if
+ // it is to be handled within reasonable
+ // time constraint, even on small systems.
+ #
+ // A small system in this regard is
+ // defined as a Raspberry Pi, which should
+ // be able to compress, encrypt and hash-map
+ // the resource in about 10 seconds.
+ #
+ // This constant will be used when determining
+ // how to sequence the sending of large resources.
+ #
+ // Capped at 16777215 (0xFFFFFF) per segment to
+ // fit in 3 bytes in resource advertisements.
+ static const uint32_t MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1;
+ static const uint8_t RESPONSE_MAX_GRACE_TIME = 10;
+
+ // The maximum size to auto-compress with
+ // bz2 before sending.
+ static const uint32_t AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE;
+
+ static const uint8_t PART_TIMEOUT_FACTOR = 4;
+ static const uint8_t PART_TIMEOUT_FACTOR_AFTER_RTT = 2;
+ static const uint8_t MAX_RETRIES = 8;
+ static const uint8_t MAX_ADV_RETRIES = 4;
+ static const uint8_t SENDER_GRACE_TIME = 10;
+ static const float RETRY_GRACE_TIME = 0.25;
+ static const float PER_RETRY_DELAY = 0.5;
+
+ static const uint8_t WATCHDOG_MAX_SLEEP = 1;
+
+ static const uint8_t HASHMAP_IS_NOT_EXHAUSTED = 0x00;
+ static const uint8_t HASHMAP_IS_EXHAUSTED = 0xFF;
+
+ // Status constants
+ enum status {
+ NONE = 0x00,
+ QUEUED = 0x01,
+ ADVERTISED = 0x02,
+ TRANSFERRING = 0x03,
+ AWAITING_PROOF = 0x04,
+ ASSEMBLING = 0x05,
+ COMPLETE = 0x06,
+ FAILED = 0x07,
+ CORRUPT = 0x08
+ };
+
+ namespace ResourceAdvertisement {
+ static const uint8_t OVERHEAD = 134;
+ static const uint16_t HASHMAP_MAX_LEN = floor((Link::MDU-OVERHEAD)/Resource::MAPHASH_LEN);
+ //static const uint16_t HASHMAP_MAX_LEN = ((Link::MDU-OVERHEAD)/Resource::MAPHASH_LEN);
+ static const uint16_t COLLISION_GUARD_SIZE = 2*Resource::WINDOW_MAX+HASHMAP_MAX_LEN;
+
+ //assert HASHMAP_MAX_LEN > 0, "The configured MTU is too small to include any map hashes in resource advertisments"
+ }
+
+ }
+
+ namespace Channel {
+ }
+
+} }

diff --git a/lib/microReticulum/src/Utilities/Crc.cpp b/lib/microReticulum/src/Utilities/Crc.cpp
new file mode 100755
index 0000000..9798546
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Crc.cpp
@@ -0,0 +1,16 @@
+#include "Crc.h"
+
+using namespace RNS::Utilities;
+
+/*static*/ uint32_t Crc::crc32(uint32_t crc, const uint8_t* buf, size_t size) {
+ const unsigned char *data = (const unsigned char *)buf;
+ if (data == NULL)
+ return 0;
+ crc ^= 0xffffffff;
+ while (size--) {
+ crc ^= *data++;
+ for (unsigned k = 0; k < 8; k++)
+ crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1;
+ }
+ return crc ^ 0xffffffff;
+}

diff --git a/lib/microReticulum/src/Utilities/Crc.h b/lib/microReticulum/src/Utilities/Crc.h
new file mode 100755
index 0000000..968f340
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Crc.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <stdint.h>
+#include <string.h>
+
+namespace RNS { namespace Utilities {
+
+ class Crc {
+
+ public:
+ static uint32_t crc32(uint32_t crc, const uint8_t* buffer, size_t size);
+ static inline uint32_t crc32(uint32_t crc, uint8_t byte) { return crc32(crc, &byte, sizeof(byte)); }
+ static inline uint32_t crc32(uint32_t crc, const char* str) { return crc32(crc, (const uint8_t*)str, strlen(str)); }
+
+ };
+
+} }

diff --git a/lib/microReticulum/src/Utilities/OS.cpp b/lib/microReticulum/src/Utilities/OS.cpp
new file mode 100755
index 0000000..3b2ece6
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/OS.cpp
@@ -0,0 +1,289 @@
+#include "OS.h"
+
+#include "../Type.h"
+#include "../Log.h"
+
+using namespace RNS;
+using namespace RNS::Utilities;
+
+
+#if defined(RNS_USE_ALLOCATOR)
+
+#if defined(RNS_USE_TLSF)
+#if defined(ESP32)
+ //#define BUFFER_SIZE 1024 * 80
+ #define BUFFER_SIZE 0
+ #define BUFFER_FRACTION 0.8
+#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
+ //#define BUFFER_SIZE 1024 * 80
+ #define BUFFER_SIZE 0
+ #define BUFFER_FRACTION 0.8
+#else
+ #define BUFFER_SIZE 1024 * 1000
+ #define BUFFER_FRACTION 0
+#endif
+
+bool _tlsf_init = false;
+//char _tlsf_msg[256] = "";
+size_t _buffer_size = BUFFER_SIZE;
+size_t _contiguous_size = 0;
+
+/*static*/ //tlsf_t OS::_tlsf = tlsf_create_with_pool(malloc(1024 * 1024), 1024 * 1024);
+/*static*/ tlsf_t OS::_tlsf = nullptr;
+#endif
+
+uint32_t _new_count = 0;
+uint32_t _new_fault = 0;
+uint64_t _new_size = 0;
+uint32_t _delete_count = 0;
+uint32_t _delete_fault = 0;
+size_t _min_size = 0;
+size_t _max_size = 0;
+
+// CBA Added attribute weak to avoid collision with new override on nrf52
+void* operator new(size_t size) {
+//__attribute__((weak)) void* operator new(size_t size) {
+#if defined(RNS_USE_TLSF)
+ //if (OS::_tlsf == nullptr) {
+ if (!_tlsf_init) {
+ _tlsf_init = true;
+#if defined(ESP32)
+ // CBA Still unknown why the call to tlsf_create_with_pool() is so flaky on ESP32 with calculated buffer size. Reuires more research and unit tests.
+ _contiguous_size = ESP.getMaxAllocHeap();
+ TRACEF("contiguous_size: %u", _contiguous_size);
+ if (_buffer_size == 0) {
+ // CBA NOTE Using fp mathhere somehow causes tlsf_create_with_pool() to fail.
+ //_buffer_size = (size_t)(_contiguous_size * BUFFER_FRACTION);
+ // Compute 80% exactly using integers
+ _buffer_size = (_contiguous_size * 4) / 5;
+ }
+ // Round DOWN to TLSF alignment
+ size_t align = tlsf_align_size();
+ _buffer_size &= ~(align - 1);
+ void* raw_buffer = (void*)aligned_alloc(align, _buffer_size);
+#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
+ _contiguous_size = dbgHeapFree();
+ TRACEF("contiguous_size: %u", _contiguous_size);
+ if (_buffer_size == 0) {
+ _buffer_size = (size_t)(_contiguous_size * BUFFER_FRACTION);
+ }
+ // For NRF52 round to kB
+ _buffer_size = (size_t)(_buffer_size / 1024) * 1024;
+ TRACEF("buffer_size: %u", _buffer_size);
+ void* raw_buffer = malloc(_buffer_size);
+#else
+ _buffer_size = (size_t)BUFFER_SIZE;
+ TRACEF("buffer_size: %u", _buffer_size);
+ void* raw_buffer = malloc(_buffer_size);
+#endif
+ if (raw_buffer == nullptr) {
+ ERROR("-- allocation for tlsf FAILED");
+ //strcpy(_tlsf_msg, "-- allocation for tlsf FAILED!!!");
+ }
+ else {
+#if 1
+ OS::_tlsf = tlsf_create_with_pool(raw_buffer, _buffer_size);
+ //if (OS::_tlsf == nullptr) {
+ // sprintf(_tlsf_msg, "initialization of tlsf with align=%d, contiguous=%d, size=%d FAILED!!!", tlsf_align_size(), _contiguous_size, _buffer_size);
+ //}
+ //else {
+ // sprintf(_tlsf_msg, "initialization of tlsf with align=%d, contiguous=%d, size=%d SUCCESSFUL!!!", tlsf_align_size(), _contiguous_size, _buffer_size);
+ //}
+#else
+ Serial.print("raw_buffer: ");
+ Serial.println((long)raw_buffer);
+ Serial.print("align_size: ");
+ Serial.println((long)tlsf_align_size());
+ void* aligned_buffer = (void*)(((size_t)raw_buffer + (tlsf_align_size() - 1)) & ~(tlsf_align_size() - 1));
+ Serial.print("aligned_buffer: ");
+ Serial.println((long)aligned_buffer);
+ OS::_tlsf = tlsf_create_with_pool(aligned_buffer, BUFFER_SIZE-(size_t)((uint32_t)aligned_buffer - (uint32_t)raw_buffer));
+ //tlfs = tlsf_create_with_pool(aligned_buffer, buffer_size--(size_t)((uint32_t)aligned_buffer - (uint32_t)raw_buffer));
+#endif
+ if (OS::_tlsf == nullptr) {
+ ERROR("-- initialization of tlsf FAILED");
+ }
+ }
+ }
+#endif
+ ++_new_count;
+ _new_size += size;
+ if (size < _min_size || _min_size == 0) {
+ _min_size = size;
+ }
+ //if (size > _max_size) {
+ if (size < 4192 && size > _max_size) {
+ _max_size = size;
+ }
+ void* p;
+#if defined(RNS_USE_TLSF)
+ if (OS::_tlsf != nullptr) {
+ //TRACEF("--- allocating memory from tlsf (%u bytes)", size);
+ p = tlsf_malloc(OS::_tlsf, size);
+ //TRACEF("--- allocated memory from tlsf (%u bytes) (addr=%lx)", size, p);
+ }
+ else {
+ //TRACEF("--- allocating memory (%u bytes)", size);
+ p = malloc(size);
+ //TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p);
+ ++_new_fault;
+ }
+#else
+ //TRACEF("--- allocating memory (%u bytes)", size);
+ p = malloc(size);
+ //TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p);
+#endif
+ return p;
+}
+
+// CBA Added attribute weak to avoid collision with new override on nrf52
+void operator delete(void* p) {
+//__attribute__((weak)) void operator delete(void* p) {
+#if defined(RNS_USE_TLSF)
+ if (OS::_tlsf != nullptr) {
+ //TRACEF("--- freeing memory from tlsf (addr=%lx)", p);
+ tlsf_free(OS::_tlsf, p);
+ }
+ else {
+ //TRACEF("--- freeing memory (addr=%lx)", p);
+ free(p);
+ ++_delete_fault;
+ }
+#else
+ //TRACEF("--- freeing memory (addr=%lx)", p);
+ //TRACE("--- freeing memory");
+ free(p);
+#endif
+ ++_delete_count;
+#if defined(RNS_USE_TLSF)
+ //if (_delete_count == _new_count) {
+ // TRACE("TLFS deinitializing");
+ // OS::dump_memory_stats();
+ // tlsf_destroy(OS::_tlsf);
+ // OS::_tlsf = nullptr;
+ //}
+#endif
+}
+
+#if defined(RNS_USE_TLSF)
+uint32_t _tlsf_used_count = 0;
+uint32_t _tlsf_used_size = 0;
+uint32_t _tlsf_free_count = 0;
+uint32_t _tlsf_free_size = 0;
+uint32_t _tlsf_free_max_size = 0;
+void tlsf_mem_walker(void* ptr, size_t size, int used, void* user)
+{
+ if (used) {
+ _tlsf_used_count++;
+ _tlsf_used_size += size;
+ }
+ else {
+ _tlsf_free_count++;
+ _tlsf_free_size += size;
+ if (size > _tlsf_free_max_size) {
+ _tlsf_free_max_size = size;
+ }
+ }
+}
+void dump_tlsf_stats() {
+ _tlsf_used_count = 0;
+ _tlsf_used_size = 0;
+ _tlsf_free_count = 0;
+ _tlsf_free_size = 0;
+ _tlsf_free_max_size = 0;
+ //TRACEF("TLSF Message: %s", _tlsf_msg);
+ if (OS::_tlsf == nullptr) {
+ return;
+ }
+ tlsf_walk_pool(tlsf_get_pool(OS::_tlsf), tlsf_mem_walker, nullptr);
+ HEAD("TLSF Stats", LOG_TRACE);
+ TRACEF("Buffer Size: %u", _buffer_size);
+ TRACEF("Contiguous Size: %u", _contiguous_size);
+ TRACEF("Used Count: %u", _tlsf_used_count);
+ TRACEF("Used Size: %u (%u%% used)", _tlsf_used_size, (unsigned)((double)_tlsf_used_size / (double)_buffer_size * 100.0));
+ TRACEF("Free Count: %u", _tlsf_free_count);
+ TRACEF("Free Size: %u (%u%% free)", _tlsf_free_size, (unsigned)((double)_tlsf_free_size / (double)_buffer_size * 100.0));
+ TRACEF("Max Free Size: %u (%u%% fragmented)\n", _tlsf_free_max_size, (unsigned)(100.0 - (double)_tlsf_free_max_size / (double)_tlsf_free_size * 100.0));
+}
+#endif
+
+/*static*/ void OS::dump_allocator_stats() {
+ HEAD("Allocator Stats", LOG_TRACE);
+ TRACEF("New Count: %u", _new_count);
+ TRACEF("New Fault: %u", _new_fault);
+ TRACEF("Delete Count: %u", _delete_count);
+ TRACEF("Delete Fault: %u", _delete_fault);
+ TRACEF("Active Count: %u", (_new_count - _delete_count));
+ TRACEF("Min Size: %u", _min_size);
+ TRACEF("Max Size: %u", _max_size);
+ TRACEF("Avg Size: %u\n", (size_t)(_new_size / _new_count));
+#if defined(RNS_USE_TLSF)
+ dump_tlsf_stats();
+#endif
+}
+
+#endif // RNS_USE_ALLOCATOR
+
+
+size_t maxContiguousAllocation() {
+ // Brute-force determine maximum allocation size
+ //const size_t block_size = 256;
+ const size_t block_size = 32;
+ size_t block_count;
+ for (block_count = 1; ; block_count++) {
+ void* ptr = malloc(block_count * block_size);
+ if (ptr == nullptr) {
+ break;
+ }
+ free(ptr);
+ }
+ return (block_count - 1) * block_size;
+}
+
+/*static*/ FileSystem OS::_filesystem = {Type::NONE};
+/*static*/ uint64_t OS::_time_offset = 0;
+
+/*static*/ size_t OS::heap_size() {
+#if defined(ESP32)
+ return ESP.getHeapSize();
+#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
+ return dbgHeapTotal();
+#else
+ return 0;
+#endif
+}
+
+/*static*/ size_t OS::heap_available() {
+#if defined(ESP32)
+ return ESP.getFreeHeap();
+ //return ESP.getMaxAllocHeap();
+#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
+ return dbgHeapFree();
+#else
+ return 0;
+#endif
+}
+
+/*static*/ void OS::dump_heap_stats() {
+ HEAD("Heap Stats", LOG_TRACE);
+#if defined(ESP32)
+ TRACEF("Heap size: %u", ESP.getHeapSize());
+ TRACEF("Heap free: %u (%u%% free)", ESP.getFreeHeap(), (unsigned)((double)ESP.getFreeHeap() / (double)ESP.getHeapSize() * 100.0));
+ //TRACEF("Heap free: %u (%u%% free)", xPortGetFreeHeapSize(), (unsigned)((double)xPortGetFreeHeapSize() / (double)xPort * 100.0));
+ TRACEF("Heap min free: %u", ESP.getMinFreeHeap());
+ //TRACEF("Heap min free: %u", xPortGetMinimumEverFreeHeapSize());
+ TRACEF("Heap max alloc: %u (%u%% fragmented)", ESP.getMaxAllocHeap(), (unsigned)(100.0 - (double)ESP.getMaxAllocHeap() / (double)ESP.getFreeHeap() * 100.0));
+ //TRACEF("Heap max alloc: %u (%u%% fragmented)", ESP.getMaxAllocHeap(), (unsigned)(100.0 - (double)ESP.getMaxAllocHeap() / (double)xPortGetFreeHeapSize() * 100.0));
+ TRACEF("PSRAM size: %u", ESP.getPsramSize());
+ TRACEF("PSRAM free: %u (%u%% free)", ESP.getFreePsram(), (ESP.getPsramSize() > 0) ? (unsigned)((double)ESP.getFreePsram() / (double)ESP.getPsramSize() * 100.0) : 0);
+ TRACEF("PSRAM min free: %u", ESP.getMinFreePsram());
+ TRACEF("PSRAM max alloc: %u (%u%% fragmented)", ESP.getMaxAllocPsram(), (ESP.getFreePsram() > 0) ? (unsigned)(100.0 - (double)ESP.getMaxAllocPsram() / (double)ESP.getFreePsram() * 100.0) : 0);
+#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
+ if (loglevel() == LOG_TRACE) {
+ dbgMemInfo();
+ }
+#endif
+#if defined(RNS_USE_ALLOCATOR)
+ OS::dump_allocator_stats();
+#endif
+}

diff --git a/lib/microReticulum/src/Utilities/OS.h b/lib/microReticulum/src/Utilities/OS.h
new file mode 100755
index 0000000..0ccb9d5
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/OS.h
@@ -0,0 +1,234 @@
+#pragma once
+
+#include "../FileSystem.h"
+#include "../FileStream.h"
+#include "../Bytes.h"
+
+#include "tlsf.h"
+
+#include <cmath>
+#include <unistd.h>
+#include <time.h>
+#include <stdint.h>
+#include <sys/time.h>
+
+#ifdef ARDUINO
+#include <Arduino.h>
+#endif
+
+#undef round
+
+namespace RNS { namespace Utilities {
+
+ class OS {
+
+ private:
+ static FileSystem _filesystem;
+ static uint64_t _time_offset;
+
+ public:
+ static tlsf_t _tlsf;
+
+ public:
+ static inline uint64_t getTimeOffset() { return _time_offset; }
+ static inline void setTimeOffset(uint64_t offset) { _time_offset = offset; }
+
+#ifdef ARDUINO
+ // return current time in milliseconds since startup
+ static inline uint64_t ltime() {
+ // handle roll-over of 32-bit millis (approx. 49 days)
+ static uint32_t low32, high32;
+ uint32_t new_low32 = millis();
+ if (new_low32 < low32) high32++;
+ low32 = new_low32;
+ return ((uint64_t)high32 << 32 | low32) + _time_offset;
+ }
+#else
+ // return current time in milliseconds since 00:00:00, January 1, 1970 (Unix Epoch)
+ static inline uint64_t ltime() { timeval time; ::gettimeofday(&time, NULL); return (uint64_t)(time.tv_sec * 1000) + (uint64_t)(time.tv_usec / 1000); }
+#endif
+
+#ifdef ARDUINO
+ // return current time in float seconds since startup
+ static inline double time() { return (double)(ltime() / 1000.0); }
+#else
+ // return current time in float seconds since 00:00:00, January 1, 1970 (Unix Epoch)
+ static inline double time() { timeval time; ::gettimeofday(&time, NULL); return (double)time.tv_sec + ((double)time.tv_usec / 1000000); }
+#endif
+
+ // sleep for specified milliseconds
+ //static inline void sleep(float seconds) { ::sleep(seconds); }
+#ifdef ARDUINO
+ static inline void sleep(float seconds) { delay((uint32_t)(seconds * 1000)); }
+#else
+ static inline void sleep(float seconds) { timespec time; time.tv_sec = (time_t)(seconds); time.tv_nsec = (seconds - (float)time.tv_sec) * 1000000000; ::nanosleep(&time, nullptr); }
+#endif
+ //static inline void sleep(uint32_t milliseconds) { ::sleep((float)milliseconds / 1000.0); }
+
+ // round decimal number to specified precision
+ //static inline float round(float value, uint8_t precision) { return std::round(value / precision) * precision; }
+ //static inline double round(double value, uint8_t precision) { return std::round(value / precision) * precision; }
+ static inline double round(double value, uint8_t precision) { return std::round(value / precision) * precision; }
+
+ static inline uint64_t from_bytes_big_endian(const uint8_t* data, size_t len) {
+ uint64_t result = 0;
+ for (size_t i = 0; i < len; ++i) {
+ result = (result << 8) | data[i];
+ }
+ return result;
+ }
+
+ // Detect endianness at runtime
+ static int is_big_endian(void) {
+ uint16_t test = 0x0102;
+ return ((uint8_t*)&test)[0] == 0x01;
+ }
+
+ // Byte swap functions
+ static uint16_t swap16(uint16_t val) {
+ return (val << 8) | (val >> 8);
+ }
+
+ static uint32_t swap32(uint32_t val) {
+ return ((val << 24) & 0xFF000000) |
+ ((val << 8) & 0x00FF0000) |
+ ((val >> 8) & 0x0000FF00) |
+ ((val >> 24) & 0x000000FF);
+ }
+
+ // Platform-independent replacements
+
+ static uint16_t portable_htons(uint16_t val) {
+ return is_big_endian() ? val : swap16(val);
+ }
+
+ static uint32_t portable_htonl(uint32_t val) {
+ return is_big_endian() ? val : swap32(val);
+ }
+
+ static uint16_t portable_ntohs(uint16_t val) {
+ return is_big_endian() ? val : swap16(val);
+ }
+
+ static uint32_t portable_ntohl(uint32_t val) {
+ return is_big_endian() ? val : swap32(val);
+ }
+
+#if defined(RNS_USE_ALLOCATOR)
+ static void dump_allocator_stats();
+#endif
+
+ inline static void register_filesystem(FileSystem& filesystem) {
+ TRACE("Registering filesystem...");
+ _filesystem = filesystem;
+ }
+
+/*
+ inline static void register_filesystem(FileSystemImpl* filesystemimpl) {
+ TRACE("Registering filesystem...");
+ _filesystem = filesystemimpl;
+ }
+*/
+
+ inline static void deregister_filesystem() {
+ TRACE("Deregistering filesystem...");
+ _filesystem = {Type::NONE};
+ }
+
+ inline static FileSystem& get_filesystem() {
+ return _filesystem;
+ }
+
+
+ inline static bool file_exists(const char* file_path) {
+ if (!_filesystem) {
+ WARNING("file_exists: filesystem not registered");
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.file_exists(file_path);
+ }
+
+ inline static size_t read_file(const char* file_path, Bytes& data) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.read_file(file_path, data);
+ }
+
+ inline static size_t write_file(const char* file_path, const Bytes& data) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.write_file(file_path, data);
+ }
+
+ inline static FileStream open_file(const char* file_path, RNS::FileStream::MODE file_mode) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.open_file(file_path, file_mode);
+ }
+
+ inline static bool remove_file(const char* file_path) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.remove_file(file_path);
+ }
+
+ inline static bool rename_file(const char* from_file_path, const char* to_file_path) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.rename_file(from_file_path, to_file_path);
+ }
+
+ inline static bool directory_exists(const char* directory_path) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.directory_exists(directory_path);
+ }
+
+ inline static bool create_directory(const char* directory_path) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.create_directory(directory_path);
+ }
+
+ inline static bool remove_directory(const char* directory_path) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.remove_directory(directory_path);
+ }
+
+ inline static std::list<std::string> list_directory(const char* directory_path) {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.list_directory(directory_path);
+ }
+
+ inline static size_t storage_size() {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.storage_size();
+ }
+
+ inline static size_t storage_available() {
+ if (!_filesystem) {
+ throw std::runtime_error("FileSystem has not been registered");
+ }
+ return _filesystem.storage_available();
+ }
+
+ static size_t heap_size();
+ static size_t heap_available();
+ static void dump_heap_stats();
+
+ };
+
+} }

diff --git a/lib/microReticulum/src/Utilities/Persistence.cpp b/lib/microReticulum/src/Utilities/Persistence.cpp
new file mode 100755
index 0000000..4c835a3
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Persistence.cpp
@@ -0,0 +1,9 @@
+#include "Persistence.h"
+
+#include "Bytes.h"
+
+using namespace RNS;
+
+/*static*/ //DynamicJsonDocument _document(Type::Persistence::DOCUMENT_MAXSIZE);
+/*static*/ JsonDocument _document;
+/*static*/ Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE);

diff --git a/lib/microReticulum/src/Utilities/Persistence.h b/lib/microReticulum/src/Utilities/Persistence.h
new file mode 100755
index 0000000..cdf0c89
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Persistence.h
@@ -0,0 +1,699 @@
+#pragma once
+
+// CBA NOTE If headers for classes referenced in this file are not included here,
+// then they MUST be included BEFORE this header is included.
+#include "Transport.h"
+#include "Type.h"
+
+#include <ArduinoJson.h>
+
+#include <map>
+#include <vector>
+#include <set>
+#include <string>
+
+namespace ArduinoJson {
+
+ // ArduinoJSON serialization support for std::vector<T>
+ template <typename T>
+ struct Converter<std::vector<T>> {
+ static void toJson(const std::vector<T>& src, JsonVariant dst) {
+ TRACE("<<< Serializing vector");
+ JsonArray array = dst.to<JsonArray>();
+ for (T item : src)
+ array.add(item);
+ TRACE("<<< Finished serializing vector");
+ }
+ static std::vector<T> fromJson(JsonVariantConst src) {
+ TRACE("<<< Deserializing vector");
+ std::vector<T> dst;
+ for (T item : src.as<JsonArrayConst>())
+ dst.push_back(item);
+ TRACE("<<< Finished deserializing vector");
+ return dst;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ JsonArrayConst array = src;
+ bool result = array;
+ for (JsonVariantConst item : array)
+ result &= item.is<T>();
+ return result;
+ }
+ };
+
+ // ArduinoJSON serialization support for std::set<T>
+ template <typename T>
+ struct Converter<std::set<T>> {
+ static void toJson(const std::set<T>& src, JsonVariant dst) {
+ JsonArray array = dst.to<JsonArray>();
+ for (T item : src)
+ array.add(item);
+ }
+ static std::set<T> fromJson(JsonVariantConst src) {
+ std::set<T> dst;
+ for (T item : src.as<JsonArrayConst>())
+ dst.insert(item);
+ return dst;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ JsonArrayConst array = src;
+ bool result = array;
+ for (JsonVariantConst item : array)
+ result &= item.is<T>();
+ return result;
+ }
+ };
+
+ // ArduinoJSON serialization support for std::map<std::string, T>
+ template <typename T>
+ struct Converter<std::map<std::string, T>> {
+ static void toJson(const std::map<std::string, T>& src, JsonVariant dst) {
+ JsonObject obj = dst.to<JsonObject>();
+ for (const auto& item : src)
+ obj[item.first] = item.second;
+ }
+ static std::map<std::string, T> fromJson(JsonVariantConst src) {
+ std::map<std::string, T> dst;
+ for (JsonPairConst item : src.as<JsonObjectConst>())
+ dst[item.key().c_str()] = item.value().as<T>();
+ return dst;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ JsonObjectConst obj = src;
+ bool result = obj;
+ for (JsonPairConst item : obj)
+ result &= item.value().is<T>();
+ return result;
+ }
+ };
+
+ // ArduinoJSON serialization support for std::map<Bytes, T>
+ template <typename T>
+ struct Converter<std::map<RNS::Bytes, T>> {
+ static void toJson(const std::map<RNS::Bytes, T>& src, JsonVariant dst) {
+ JsonObject obj = dst.to<JsonObject>();
+ for (const auto& item : src) {
+ //obj[item.first] = item.second;
+ obj[item.first.toHex()] = item.second;
+ }
+ }
+ static std::map<RNS::Bytes, T> fromJson(JsonVariantConst src) {
+ std::map<RNS::Bytes, T> dst;
+ for (JsonPairConst item : src.as<JsonObjectConst>()) {
+ //dst[item.key().c_str()] = item.value().as<T>();
+ RNS::Bytes key;
+ key.assignHex(item.key().c_str());
+ //dst[key] = item.value().as<T>();
+ dst.insert({key, item.value().as<T>()});
+ }
+ return dst;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ JsonObjectConst obj = src;
+ bool result = obj;
+ for (JsonPairConst item : obj) {
+ result &= item.value().is<T>();
+ }
+ return result;
+ }
+ };
+
+/*
+ // ArduinoJSON serialization support for RNS::Bytes
+ template <>
+ struct Converter<RNS::Bytes> {
+ static bool toJson(const RNS::Bytes& src, JsonVariant dst) {
+ //TRACE("<<< Serializing Bytes");
+ //TRACE("<<< Finished serializing Bytes");
+ return true;
+ }
+ static RNS::Bytes fromJson(JsonVariantConst src) {
+ Bytes bytes;
+ //TRACE(">>> Deserializing Bytes");
+ //TRACE(">>> Finished deserializing Bytes");
+ return bytes;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return
+ src.is<RNS::Bytes>();
+ }
+ };
+*/
+
+/*
+ // ArduinoJSON serialization support for RNS::Interface
+ template <>
+ struct Converter<RNS::Interface> {
+ static bool toJson(const RNS::Interface& src, JsonVariant dst) {
+ if (!src) {
+ return dst.set(nullptr);
+ }
+ //TRACE("<<< Serializing interface hash " + src.get_hash().toHex());
+ return dst.set(src.get_hash().toHex());
+ }
+ static RNS::Interface fromJson(JsonVariantConst src) {
+ if (!src.isNull()) {
+ RNS::Bytes hash;
+ hash.assignHex(src.as<const char*>());
+ //TRACE(">>> Deserialized interface hash " + hash.toHex());
+ //TRACE(">>> Querying transport for interface");
+ // Query transport for matching interface
+ return RNS::Transport::find_interface_from_hash(hash);
+ }
+ else {
+ return {RNS::Type::NONE};
+ }
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
+ }
+ };
+*/
+
+/*
+ // ArduinoJSON serialization support for RNS::Packet
+ template <>
+ struct Converter<RNS::Packet> {
+ static bool toJson(const RNS::Packet& src, JsonVariant dst) {
+ if (!src) {
+ return dst.set(nullptr);
+ }
+ //TRACE("<<< Serializing packet hash " + src.get_hash().toHex());
+ // Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
+ RNS::Transport::cache_packet(src, true);
+ return dst.set(src.get_hash().toHex());
+ }
+ static RNS::Packet fromJson(JsonVariantConst src) {
+ if (!src.isNull()) {
+ RNS::Bytes hash;
+ hash.assignHex(src.as<const char*>());
+ //TRACE(">>> Deserialized packet hash " + hash.toHex());
+ //TRACE(">>> Querying transport for cached packet");
+ // Query transport for matching packet
+ return RNS::Transport::get_cached_packet(hash);
+ }
+ else {
+ return {RNS::Type::NONE};
+ }
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
+ }
+ };
+*/
+ // ArduinoJSON serialization support for RNS::Packet
+ template <>
+ struct Converter<RNS::Packet> {
+ static bool toJson(const RNS::Packet& src, JsonVariant dst) {
+ //TRACE("<<< Serializing Packet");
+ //dst["hash"] = src.get_hash();
+ dst["raw"] = src.raw();
+ dst["sent_at"] = src.sent_at();
+ dst["destination_hash"] = src.get_hash();
+ //TRACE("<<< Finished serializing Packet");
+ return true;
+ }
+ static RNS::Packet fromJson(JsonVariantConst src) {
+ //TRACE(">>> Deserializing Packet");
+ RNS::Bytes raw = src["raw"];
+ //RNS::Bytes raw = src["raw"].as<const RNS::Bytes&>();
+ RNS::Packet packet(RNS::Destination(RNS::Type::NONE), raw);
+ //packet.set_hash(src["hash"]);
+ packet.sent_at(src["sent_at"]);
+ RNS::Bytes destination_hash = src["destination_hash"];
+ // set cached flag since pcket was read from cache
+ packet.cached(true);
+ //TRACE(">>> Finished deserializing Packet");
+ return packet;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return
+ //src["hash"].is<RNS::Bytes>() &&
+ src["raw"].is<RNS::Bytes>() &&
+ src["sent_at"].is<double>() &&
+ src["destination_hash"].is<RNS::Bytes>();
+ }
+ };
+
+ // ArduinoJSON serialization support for RNS::Transport::PacketEntry
+ template <>
+ struct Converter<RNS::Transport::PacketEntry> {
+ static bool toJson(const RNS::Transport::PacketEntry& src, JsonVariant dst) {
+ //TRACE("<<< Serializing Transport::PacketEntry");
+ //dst["hash"] = src._hash;
+ dst["raw"] = src._raw;
+ dst["sent_at"] = src._sent_at;
+ dst["destination_hash"] = src._destination_hash;
+ //TRACE("<<< Finished serializing Transport::PacketEntry");
+ return true;
+ }
+ static RNS::Transport::PacketEntry fromJson(JsonVariantConst src) {
+ //TRACE(">>> Deserializing Transport::PacketEntry");
+ RNS::Transport::PacketEntry dst;
+ //dst._hash = src["hash"];
+ dst._raw = src["raw"];
+ dst._sent_at = src["sent_at"];
+ dst._destination_hash = src["destination_hash"];
+ // set cached flag since pcket was read from cache
+ dst._cached = true;
+ //TRACE(">>> Finished deserializing Transport::PacketEntry");
+ return dst;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return
+ //src["hash"].is<RNS::Bytes>() &&
+ src["raw"].is<RNS::Bytes>() &&
+ src["sent_at"].is<double>() &&
+ src["destination_hash"].is<RNS::Bytes>();
+ }
+ };
+
+#if 1
+ // ArduinoJSON serialization support for RNS::Transport::DestinationEntry
+ template <>
+ struct Converter<RNS::Transport::DestinationEntry> {
+ static bool toJson(const RNS::Transport::DestinationEntry& src, JsonVariant dst) {
+ //TRACE("<<< Serializing Transport::DestinationEntry");
+ dst["timestamp"] = src._timestamp;
+ dst["received_from"] = src._received_from;
+ dst["announce_hops"] = src._hops;
+ dst["expires"] = src._expires;
+ dst["random_blobs"] = src._random_blobs;
+/*
+ //dst["interface_hash"] = src._receiving_interface;
+ if (src._receiving_interface) {
+ dst["interface_hash"] = src._receiving_interface.get_hash();
+ }
+ else {
+ dst["interface_hash"] = nullptr;
+ }
+ // CBA TODO Move packet serialization to *after* destination table serialization since packets are useless
+ // anyway if there's no space to left to write destination table.
+ // Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
+ //dst["packet"] = src._announce_packet;
+ if (src._announce_packet) {
+ dst["packet_hash"] = src._announce_packet.get_hash();
+ // Only cache packet if not already cached
+ if (!src._announce_packet.cached()) {
+ if (RNS::Transport::cache_packet(src._announce_packet, true)) {
+ const_cast<RNS::Packet&>(src._announce_packet).cached(true);
+ }
+ }
+ else {
+ TRACE("Destination announce packet " + src._announce_packet.get_hash().toHex() + " is already cached");
+ }
+ }
+ else {
+ dst["packet_hash"] = nullptr;
+ }
+*/
+ dst["interface_hash"] = src._receiving_interface;
+ dst["packet_hash"] = src._announce_packet;
+ //TRACE("<<< Finished Serializing Transport::DestinationEntry");
+ return true;
+ }
+ static RNS::Transport::DestinationEntry fromJson(JsonVariantConst src) {
+ //TRACE(">>> Deserializing Transport::DestinationEntry");
+ RNS::Transport::DestinationEntry dst;
+ dst._timestamp = src["timestamp"];
+ dst._received_from = src["received_from"];
+ dst._hops = src["announce_hops"];
+ dst._expires = src["expires"];
+ dst._random_blobs = src["random_blobs"].as<std::set<RNS::Bytes>>();
+/*
+ //dst._receiving_interface = src["interface_hash"];
+ RNS::Bytes interface_hash = src["interface_hash"];
+ if (interface_hash) {
+ // Query transport for matching interface
+ dst._receiving_interface = RNS::Transport::find_interface_from_hash(interface_hash);
+ }
+ //dst._announce_packet = src["packet"];
+ RNS::Bytes packet_hash = src["packet_hash"];
+ if (packet_hash) {
+ // Query transport for matching packet
+ dst._announce_packet = RNS::Transport::get_cached_packet(packet_hash);
+ }
+*/
+ dst._receiving_interface = src["interface_hash"];
+ dst._announce_packet = src["packet_hash"];
+/*
+ //RNS::Transport::DestinationEntry dst(src["timestamp"], src["received_from"], src["announce_hops"], src["expires"], src["random_blobs"], src["receiving_interface"], src["packet"]);
+ RNS::Transport::DestinationEntry dst(
+ src["timestamp"].as<double>(),
+ src["received_from"].as<RNS::Bytes>(),
+ src["announce_hops"].as<int>(),
+ src["expires"].as<double>(),
+ src["random_blobs"].as<std::set<RNS::Bytes>>(),
+ src["receiving_interface"].as<RNS::Interface>(),
+ src["packet"].as<RNS::Packet>()
+ );
+*/
+ //TRACE(">>> Finished Deserializing Transport::DestinationEntry");
+ return dst;
+ }
+ static bool checkJson(JsonVariantConst src) {
+ return
+ src["timestamp"].is<double>() &&
+ src["received_from"].is<RNS::Bytes>() &&
+ src["announce_hops"].is<int>() &&
+ src["expires"].is<double>() &&
+ src["random_blobs"].is<std::set<RNS::Bytes>>() &&
+ src["interface_hash"].is<RNS::Bytes>() &&
+ src["packet_hash"].is<RNS::Bytes>();
+ }
+ };
+#endif
+
+}
+
+#if 0
+namespace RNS {
+
+ inline bool convertToJson(const Transport::DestinationEntry& src, JsonVariant dst) {
+ //TRACE("<<< NEW Serializing Transport::DestinationEntry");
+ dst["timestamp"] = src._timestamp;
+ dst["received_from"] = src._received_from;
+ dst["announce_hops"] = src._hops;
+ dst["expires"] = src._expires;
+ dst["random_blobs"] = src._random_blobs;
+/*
+ //dst["interface_hash"] = src._receiving_interface;
+ if (src._receiving_interface) {
+ dst["interface_hash"] = src._receiving_interface.get_hash();
+ }
+ else {
+ dst["interface_hash"] = nullptr;
+ }
+ // CBA TODO Move packet serialization to *after* destination table serialization since packets are useless
+ // anyway if there's no space to left to write destination table.
+ // Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
+ //dst["packet"] = src._announce_packet;
+ if (src._announce_packet) {
+ dst["packet_hash"] = src._announce_packet.get_hash();
+ // Only cache packet if not already cached
+ if (!src._announce_packet.cached()) {
+ if (RNS::Transport::cache_packet(src._announce_packet, true)) {
+ const_cast<RNS::Packet&>(src._announce_packet).cached(true);
+ }
+ }
+ else {
+ TRACE("Destination announce packet " + src._announce_packet.get_hash().toHex() + " is already cached");
+ }
+ }
+ else {
+ dst["packet_hash"] = nullptr;
+ }
+*/
+ dst["interface_hash"] = src._receiving_interface;
+ dst["packet_hash"] = src._announce_packet;
+ //TRACE("<<< Finished Serializing Transport::DestinationEntry");
+ return true;
+ }
+
+ inline void convertFromJson(JsonVariantConst src, Transport::DestinationEntry& dst) {
+ //TRACE(">>> NEW Deserializing Transport::DestinationEntry");
+ dst._timestamp = src["timestamp"];
+ dst._received_from = src["received_from"];
+ dst._hops = src["announce_hops"];
+ dst._expires = src["expires"];
+ dst._random_blobs = src["random_blobs"].as<std::set<RNS::Bytes>>();
+/*
+ //dst._receiving_interface = src["interface_hash"];
+ RNS::Bytes interface_hash = src["interface_hash"];
+ if (interface_hash) {
+ // Query transport for matching interface
+ dst._receiving_interface = RNS::Transport::find_interface_from_hash(interface_hash);
+ }
+ //dst._announce_packet = src["packet"];
+ RNS::Bytes packet_hash = src["packet_hash"];
+ if (packet_hash) {
+ // Query transport for matching packet
+ dst._announce_packet = RNS::Transport::get_cached_packet(packet_hash);
+ }
+*/
+ dst._receiving_interface = src["interface_hash"];
+ dst._announce_packet = src["packet_hash"];
+/*
+ //RNS::Transport::DestinationEntry dst(src["timestamp"], src["received_from"], src["announce_hops"], src["expires"], src["random_blobs"], src["receiving_interface"], src["packet"]);
+ RNS::Transport::DestinationEntry dst(
+ src["timestamp"].as<double>(),
+ src["received_from"].as<RNS::Bytes>(),
+ src["announce_hops"].as<int>(),
+ src["expires"].as<double>(),
+ src["random_blobs"].as<std::set<RNS::Bytes>>(),
+ src["receiving_interface"].as<RNS::Interface>(),
+ src["packet"].as<RNS::Packet>()
+ );
+*/
+ //TRACE(">>> Finished Deserializing Transport::DestinationEntry");
+ }
+
+ inline bool canConvertFromJson(JsonVariantConst src, const Transport::DestinationEntry&) {
+ TRACE("=== NEW Checking Transport::DestinationEntry");
+ return
+ src["timestamp"].is<double>() &&
+ src["received_from"].is<RNS::Bytes>() &&
+ src["announce_hops"].is<int>() &&
+ src["expires"].is<double>() &&
+ src["random_blobs"].is<std::set<RNS::Bytes>>() &&
+ src["interface_hash"].is<RNS::Bytes>() &&
+ src["packet_hash"].is<RNS::Bytes>();
+ }
+}
+#endif
+
+namespace RNS { namespace Persistence {
+
+ //static DynamicJsonDocument _document(Type::Persistence::DOCUMENT_MAXSIZE);
+ static JsonDocument _document;
+ static Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE);
+
+ template <typename T> size_t crc(const T& obj) {
+ //TRACE("Persistence::crc<T>");
+ _document.set(obj);
+ size_t size = _buffer.capacity();
+#ifdef USE_MSGPACK
+ size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
+#else
+ size_t length = serializeJson(_document, _buffer.writable(size), size);
+#endif
+ if (length < size) {
+ _buffer.resize(length);
+ }
+ TRACEF("Persistence::crc: serialized %d bytes", length);
+ return Utilities::Crc::crc32(0, _buffer.data(), _buffer.size());
+ }
+
+ template <typename T> size_t serialize(const T& obj, const char* file_path) {
+ //TRACE("Persistence::serialize<T>");
+ _document.set(obj);
+ size_t size = _buffer.capacity();
+#ifdef USE_MSGPACK
+ size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
+#else
+ size_t length = serializeJson(_document, _buffer.writable(size), size);
+#endif
+ if (length < size) {
+ _buffer.resize(length);
+ }
+ TRACEF("Persistence::serialize: serialized %d bytes", length);
+ size_t wrote = 0;
+ if (length > 0) {
+ wrote = RNS::Utilities::OS::write_file(file_path, _buffer);
+ if (wrote == _buffer.size()) {
+ TRACEF("Persistence::serialize: wrote %d bytes", _buffer.size());
+ }
+ else {
+ TRACE("Persistence::serialize: write failed");
+ return 0;
+ }
+ }
+ else {
+ TRACE("Persistence::serialize: failed to serialize");
+ return 0;
+ }
+ return wrote;
+ }
+
+ template <typename T> size_t deserialize(T& obj, const char* file_path) {
+ //TRACE("Persistence::deserialize<T>");
+ size_t read = RNS::Utilities::OS::read_file(file_path, _buffer);
+ if (read > 0) {
+ TRACEF("Persistence::deserialize: read: %d bytes", _buffer.size());
+ //TRACE("testDeserializeVector: data: " + _buffer.toString());
+#ifdef USE_MSGPACK
+ DeserializationError error = deserializeMsgPack(_document, _buffer.data());
+#else
+ DeserializationError error = deserializeJson(_document, _buffer.data());
+#endif
+ if (!error) {
+ TRACE("Persistence::deserialize: successfully deserialized document");
+ obj = _document.as<T>();
+ // CBA Following obj check doesn't work when T is a collection
+ //if (obj) {
+ return read;
+ //}
+ TRACE("Persistence::deserialize: failed to compose object");
+ }
+ else {
+ TRACE("Persistence::deserialize: failed to deserialize");
+ }
+ }
+ else {
+ TRACE("Persistence::deserialize: read failed");
+ }
+ return 0;
+ }
+
+#if 1
+ template <typename T> size_t crc(std::map<Bytes, T>& map) {
+ //TRACE("Persistence::crc<map<Bytes, T>>");
+
+ uint32_t crc = 0;
+ crc = Utilities::Crc::crc32(crc, '{');
+ for (const auto& [key, value] : map) {
+ crc = Utilities::Crc::crc32(crc, '"');
+ std::string hex = key.toHex();
+ crc = Utilities::Crc::crc32(crc, hex.c_str());
+ crc = Utilities::Crc::crc32(crc, '"');
+ crc = Utilities::Crc::crc32(crc, ':');
+
+ _document.set(value);
+ size_t size = _buffer.capacity();
+#ifdef USE_MSGPACK
+ size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
+#else
+ size_t length = serializeJson(_document, _buffer.writable(size), size);
+#endif
+ if (length < size) {
+ _buffer.resize(length);
+ }
+ TRACEF("Persistence::crc: serialized entry %d bytes", length);
+
+ if (length > 0) {
+ crc = Utilities::Crc::crc32(crc, _buffer.data(), _buffer.size());
+ }
+ else {
+ // if failed to serialize entry then write empty entry
+ crc = Utilities::Crc::crc32(crc, "{}");
+ }
+ crc = Utilities::Crc::crc32(crc, ',');
+ }
+ return Utilities::Crc::crc32(crc, '}');
+ }
+
+ template <typename T> size_t serialize(std::map<Bytes, T>& map, const char* file_path, uint32_t& crc) {
+ //TRACE("Persistence::serialize<map<Bytes,T>>");
+
+ // CBA TODO: Use stream here instead to avoid having to buffer entire structure
+ RNS::FileStream stream = RNS::Utilities::OS::open_file(file_path, RNS::FileStream::MODE_WRITE);
+ if (!stream) {
+ TRACE("Persistence::serialize: failed to open write stream");
+ return 0;
+ }
+
+ stream.write('{');
+ for (const auto& [key, value] : map) {
+ stream.write('"');
+ std::string hex = key.toHex();
+ stream.write(hex.c_str());
+ stream.write('"');
+ stream.write(':');
+
+ _document.set(value);
+#ifdef USE_MSGPACK
+ size_t length = serializeMsgPack(_document, stream);
+#else
+ size_t length = serializeJson(_document, stream);
+#endif
+ TRACEF("Persistence::serialize: serialized entry %d bytes", length);
+
+ if (length == 0) {
+ // if failed to serialize entry then write empty entry
+ stream.write("{}");
+ }
+ stream.write(',');
+ }
+ stream.write('}');
+ TRACEF("Persistence::serialize: stream size: %d bytes", stream.size());
+ crc = stream.crc();
+ return stream.size();
+ }
+
+ template <typename T> size_t serialize(std::map<Bytes, T>& map, const char* file_path) {
+ uint32_t crc;
+ return serialize(map, file_path, crc);
+ }
+
+ template <typename T> size_t deserialize(std::map<Bytes, T>& map, const char* file_path, uint32_t& crc) {
+ //TRACE("Persistence::deserialize<map<Bytes,T>>");
+
+ // CBA TODO: Use stream here instead to avoid having to buffer entire structure
+ RNS::FileStream stream = RNS::Utilities::OS::open_file(file_path, RNS::FileStream::MODE_READ);
+ if (!stream) {
+ TRACE("Persistence::deserialize: failed to open read stream");
+ return 0;
+ }
+ TRACEF("Persistence::deserialize: size: %d bytes", stream.size());
+
+ map.clear();
+
+ if (stream.size() == 0) {
+ TRACE("Persistence::deserialize: read stream is empty");
+ return 0;
+ }
+
+ // find opening brace
+ if (stream.find('{')) {
+ char key_str[RNS::Type::Reticulum::DESTINATION_LENGTH*2+1] = "";
+ do {
+ key_str[0] = 0;
+ // find map key opening quote
+ if (stream.find('"')) {
+ if (stream.readBytesUntil('"', key_str, sizeof(key_str)) > 0) {
+ Bytes key;
+ key.assignHex(key_str);
+ TRACEF("Persistence::deserialize: key: %s", key.toHex().c_str());
+ if (stream.find(':')) {
+#ifdef USE_MSGPACK
+ //DeserializationError error = deserializeMsgPack(_document, _buffer.data());
+ DeserializationError error = deserializeMsgPack(_document, stream);
+#else
+ //DeserializationError error = deserializeJson(_document, _buffer.data());
+ DeserializationError error = deserializeJson(_document, stream);
+#endif
+ if (!error) {
+ TRACE("Persistence::deserialize: successfully deserialized entry");
+ T obj = _document.as<T>();
+ // CBA Following obj check doesn't work when T is a collection
+ //if (obj) {
+ // TRACE("Persistence::deserialize: failed to compose object");
+ // break;
+ //}
+ map.insert({key, obj});
+ }
+ else {
+ TRACE("Persistence::deserialize: failed to deserialize entry");
+ //break;
+ }
+
+ if (!stream.find(',')) {
+ break;
+ }
+
+ }
+ }
+ }
+ } while (key_str[0] != 0);
+ }
+ crc = stream.crc();
+ return stream.size();
+ }
+
+ template <typename T> size_t deserialize(std::map<Bytes, T>& map, const char* file_path) {
+ uint32_t crc;
+ return deserialize(map, file_path, crc);
+ }
+#endif
+
+} }

diff --git a/lib/microReticulum/src/Utilities/Print.cpp b/lib/microReticulum/src/Utilities/Print.cpp
new file mode 100755
index 0000000..7c2566d
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Print.cpp
@@ -0,0 +1,354 @@
+/*
+ Print.cpp - Base class that provides print() and println()
+ Copyright (c) 2008 David A. Mellis. All right reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ Modified 23 November 2006 by David A. Mellis
+ Modified December 2014 by Ivan Grokhotkov
+ Modified May 2015 by Michael C. Miller - ESP31B progmem support
+ */
+
+#ifndef ARDUINO
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+
+#include "Print.h"
+extern "C" {
+ #include "time.h"
+}
+
+// Public Methods //////////////////////////////////////////////////////////////
+
+/* default implementation: may be overridden */
+size_t Print::write(const uint8_t *buffer, size_t size)
+{
+ size_t n = 0;
+ while(size--) {
+ n += write(*buffer++);
+ }
+ return n;
+}
+
+size_t Print::printf(const char *format, ...)
+{
+ char loc_buf[64];
+ char * temp = loc_buf;
+ va_list arg;
+ va_list copy;
+ va_start(arg, format);
+ va_copy(copy, arg);
+ int len = vsnprintf(temp, sizeof(loc_buf), format, copy);
+ va_end(copy);
+ if(len < 0) {
+ va_end(arg);
+ return 0;
+ }
+ if(len >= (int)sizeof(loc_buf)){ // comparation of same sign type for the compiler
+ temp = (char*) malloc(len+1);
+ if(temp == NULL) {
+ va_end(arg);
+ return 0;
+ }
+ len = vsnprintf(temp, len+1, format, arg);
+ }
+ va_end(arg);
+ len = write((uint8_t*)temp, len);
+ if(temp != loc_buf){
+ free(temp);
+ }
+ return len;
+}
+
+size_t Print::print(const std::string &s)
+{
+ return write(s.c_str(), s.length());
+}
+
+size_t Print::print(const char str[])
+{
+ return write(str);
+}
+
+size_t Print::print(char c)
+{
+ return write(c);
+}
+
+size_t Print::print(unsigned char b, int base)
+{
+ return print((unsigned long) b, base);
+}
+
+size_t Print::print(int n, int base)
+{
+ return print((long) n, base);
+}
+
+size_t Print::print(unsigned int n, int base)
+{
+ return print((unsigned long) n, base);
+}
+
+size_t Print::print(long n, int base)
+{
+ int t = 0;
+ if (base == 10 && n < 0) {
+ t = print('-');
+ n = -n;
+ }
+ return printNumber(static_cast<unsigned long>(n), base) + t;
+}
+
+size_t Print::print(unsigned long n, int base)
+{
+ if(base == 0) {
+ return write(n);
+ } else {
+ return printNumber(n, base);
+ }
+}
+
+size_t Print::print(long long n, int base)
+{
+ int t = 0;
+ if (base == 10 && n < 0) {
+ t = print('-');
+ n = -n;
+ }
+ return printNumber(static_cast<unsigned long long>(n), base) + t;
+}
+
+size_t Print::print(unsigned long long n, int base)
+{
+ if (base == 0) {
+ return write(n);
+ } else {
+ return printNumber(n, base);
+ }
+}
+
+size_t Print::print(double n, int digits)
+{
+ return printFloat(n, digits);
+}
+
+size_t Print::print(struct tm * timeinfo, const char * format)
+{
+ const char * f = format;
+ if(!f){
+ f = "%c";
+ }
+ char buf[64];
+ size_t written = strftime(buf, 64, f, timeinfo);
+ if(written == 0){
+ return written;
+ }
+ return print(buf);
+}
+
+size_t Print::println(void)
+{
+ return print("\r\n");
+}
+
+size_t Print::println(const std::string &s)
+{
+ size_t n = print(s);
+ n += println();
+ return n;
+}
+
+size_t Print::println(const char c[])
+{
+ size_t n = print(c);
+ n += println();
+ return n;
+}
+
+size_t Print::println(char c)
+{
+ size_t n = print(c);
+ n += println();
+ return n;
+}
+
+size_t Print::println(unsigned char b, int base)
+{
+ size_t n = print(b, base);
+ n += println();
+ return n;
+}
+
+size_t Print::println(int num, int base)
+{
+ size_t n = print(num, base);
+ n += println();
+ return n;
+}
+
+size_t Print::println(unsigned int num, int base)
+{
+ size_t n = print(num, base);
+ n += println();
+ return n;
+}
+
+size_t Print::println(long num, int base)
+{
+ size_t n = print(num, base);
+ n += println();
+ return n;
+}
+
+size_t Print::println(unsigned long num, int base)
+{
+ size_t n = print(num, base);
+ n += println();
+ return n;
+}
+
+size_t Print::println(long long num, int base)
+{
+ size_t n = print(num, base);
+ n += println();
+ return n;
+}
+
+size_t Print::println(unsigned long long num, int base)
+{
+ size_t n = print(num, base);
+ n += println();
+ return n;
+}
+
+size_t Print::println(double num, int digits)
+{
+ size_t n = print(num, digits);
+ n += println();
+ return n;
+}
+
+size_t Print::println(struct tm * timeinfo, const char * format)
+{
+ size_t n = print(timeinfo, format);
+ n += println();
+ return n;
+}
+
+// Private Methods /////////////////////////////////////////////////////////////
+
+size_t Print::printNumber(unsigned long n, uint8_t base)
+{
+ char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
+ char *str = &buf[sizeof(buf) - 1];
+
+ *str = '\0';
+
+ // prevent crash if called with base == 1
+ if(base < 2) {
+ base = 10;
+ }
+
+ do {
+ char c = n % base;
+ n /= base;
+
+ *--str = c < 10 ? c + '0' : c + 'A' - 10;
+ } while (n);
+
+ return write(str);
+}
+
+size_t Print::printNumber(unsigned long long n, uint8_t base)
+{
+ char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
+ char* str = &buf[sizeof(buf) - 1];
+
+ *str = '\0';
+
+ // prevent crash if called with base == 1
+ if (base < 2) {
+ base = 10;
+ }
+
+ do {
+ auto m = n;
+ n /= base;
+ char c = m - base * n;
+
+ *--str = c < 10 ? c + '0' : c + 'A' - 10;
+ } while (n);
+
+ return write(str);
+}
+
+size_t Print::printFloat(double number, uint8_t digits)
+{
+ size_t n = 0;
+
+ if(isnan(number)) {
+ return print("nan");
+ }
+ if(isinf(number)) {
+ return print("inf");
+ }
+ if(number > 4294967040.0) {
+ return print("ovf"); // constant determined empirically
+ }
+ if(number < -4294967040.0) {
+ return print("ovf"); // constant determined empirically
+ }
+
+ // Handle negative numbers
+ if(number < 0.0) {
+ n += print('-');
+ number = -number;
+ }
+
+ // Round correctly so that print(1.999, 2) prints as "2.00"
+ double rounding = 0.5;
+ for(uint8_t i = 0; i < digits; ++i) {
+ rounding /= 10.0;
+ }
+
+ number += rounding;
+
+ // Extract the integer part of the number and print it
+ unsigned long int_part = (unsigned long) number;
+ double remainder = number - (double) int_part;
+ n += print(int_part);
+
+ // Print the decimal point, but only if there are digits beyond
+ if(digits > 0) {
+ n += print(".");
+ }
+
+ // Extract digits from the remainder one at a time
+ while(digits-- > 0) {
+ remainder *= 10.0;
+ int toPrint = int(remainder);
+ n += print(toPrint);
+ remainder -= toPrint;
+ }
+
+ return n;
+}
+
+#endif

diff --git a/lib/microReticulum/src/Utilities/Print.h b/lib/microReticulum/src/Utilities/Print.h
new file mode 100755
index 0000000..89b026c
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Print.h
@@ -0,0 +1,121 @@
+/*
+ Print.h - Base class that provides print() and println()
+ Copyright (c) 2008 David A. Mellis. All right reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef ARDUINO
+
+#ifndef Print_h
+#define Print_h
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+
+#include <string>
+
+//#define DEC 10
+//#define HEX 16
+//#define OCT 8
+//#define BIN 2
+static constexpr uint8_t DEC {10};
+static constexpr uint8_t HEX {16};
+static constexpr uint8_t OCT {8};
+static constexpr uint8_t BIN {2};
+
+class Print
+{
+private:
+ int write_error;
+ size_t printNumber(unsigned long, uint8_t);
+ size_t printNumber(unsigned long long, uint8_t);
+ size_t printFloat(double, uint8_t);
+protected:
+ void setWriteError(int err = 1)
+ {
+ write_error = err;
+ }
+public:
+ Print() :
+ write_error(0)
+ {
+ }
+ virtual ~Print() {}
+ int getWriteError()
+ {
+ return write_error;
+ }
+ void clearWriteError()
+ {
+ setWriteError(0);
+ }
+
+ virtual size_t write(uint8_t) = 0;
+ size_t write(const char *str)
+ {
+ if(str == NULL) {
+ return 0;
+ }
+ return write((const uint8_t *) str, strlen(str));
+ }
+ virtual size_t write(const uint8_t *buffer, size_t size);
+ size_t write(const char *buffer, size_t size)
+ {
+ return write((const uint8_t *) buffer, size);
+ }
+
+ size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
+
+ // add availableForWrite to make compatible with Arduino Print.h
+ // default to zero, meaning "a single write may block"
+ // should be overriden by subclasses with buffering
+ virtual int availableForWrite() { return 0; }
+ size_t print(const std::string &);
+ size_t print(const char[]);
+ size_t print(char);
+ size_t print(unsigned char, int = DEC);
+ size_t print(int, int = DEC);
+ size_t print(unsigned int, int = DEC);
+ size_t print(long, int = DEC);
+ size_t print(unsigned long, int = DEC);
+ size_t print(long long, int = DEC);
+ size_t print(unsigned long long, int = DEC);
+ size_t print(double, int = 2);
+ size_t print(struct tm * timeinfo, const char * format = NULL);
+
+ size_t println(const std::string &s);
+ size_t println(const char[]);
+ size_t println(char);
+ size_t println(unsigned char, int = DEC);
+ size_t println(int, int = DEC);
+ size_t println(unsigned int, int = DEC);
+ size_t println(long, int = DEC);
+ size_t println(unsigned long, int = DEC);
+ size_t println(long long, int = DEC);
+ size_t println(unsigned long long, int = DEC);
+ size_t println(double, int = 2);
+ size_t println(struct tm * timeinfo, const char * format = NULL);
+ size_t println(void);
+
+ virtual void flush() { /* Empty implementation for backward compatibility */ }
+
+};
+
+#endif
+
+#endif

diff --git a/lib/microReticulum/src/Utilities/Stream.cpp b/lib/microReticulum/src/Utilities/Stream.cpp
new file mode 100755
index 0000000..56fc2a6
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Stream.cpp
@@ -0,0 +1,339 @@
+/*
+ Stream.cpp - adds parsing methods to Stream class
+ Copyright (c) 2008 David A. Mellis. All right reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ Created July 2011
+ parsing functions based on TextFinder library by Michael Margolis
+ */
+
+#ifndef ARDUINO
+
+#include "Stream.h"
+#include "OS.h"
+
+#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
+#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
+
+// private method to read stream with timeout
+int Stream::timedRead()
+{
+ int c;
+ _startMillis = RNS::Utilities::OS::ltime();
+ do {
+ c = read();
+ if(c >= 0) {
+ return c;
+ }
+ } while(RNS::Utilities::OS::ltime() - _startMillis < _timeout);
+ return -1; // -1 indicates timeout
+}
+
+// private method to peek stream with timeout
+int Stream::timedPeek()
+{
+ int c;
+ _startMillis = RNS::Utilities::OS::ltime();
+ do {
+ c = peek();
+ if(c >= 0) {
+ return c;
+ }
+ } while(RNS::Utilities::OS::ltime() - _startMillis < _timeout);
+ return -1; // -1 indicates timeout
+}
+
+// returns peek of the next digit in the stream or -1 if timeout
+// discards non-numeric characters
+int Stream::peekNextDigit()
+{
+ int c;
+ while(1) {
+ c = timedPeek();
+ if(c < 0) {
+ return c; // timeout
+ }
+ if(c == '-') {
+ return c;
+ }
+ if(c >= '0' && c <= '9') {
+ return c;
+ }
+ read(); // discard non-numeric
+ }
+}
+
+// Public Methods
+//////////////////////////////////////////////////////////////
+
+void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait
+{
+ _timeout = timeout;
+}
+unsigned long Stream::getTimeout(void) {
+ return _timeout;
+}
+
+// find returns true if the target string is found
+bool Stream::find(const char *target)
+{
+ return findUntil(target, strlen(target), NULL, 0);
+}
+
+// reads data from the stream until the target string of given length is found
+// returns true if target string is found, false if timed out
+bool Stream::find(const char *target, size_t length)
+{
+ return findUntil(target, length, NULL, 0);
+}
+
+// as find but search ends if the terminator string is found
+bool Stream::findUntil(const char *target, const char *terminator)
+{
+ return findUntil(target, strlen(target), terminator, strlen(terminator));
+}
+
+// reads data from the stream until the target string of the given length is found
+// search terminated if the terminator string is found
+// returns true if target string is found, false if terminated or timed out
+bool Stream::findUntil(const char *target, size_t targetLen, const char *terminator, size_t termLen)
+{
+ if (terminator == NULL) {
+ MultiTarget t[1] = {{target, targetLen, 0}};
+ return findMulti(t, 1) == 0 ? true : false;
+ } else {
+ MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}};
+ return findMulti(t, 2) == 0 ? true : false;
+ }
+}
+
+int Stream::findMulti( struct Stream::MultiTarget *targets, int tCount) {
+ // any zero length target string automatically matches and would make
+ // a mess of the rest of the algorithm.
+ for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
+ if (t->len <= 0)
+ return t - targets;
+ }
+
+ while (1) {
+ int c = timedRead();
+ if (c < 0)
+ return -1;
+
+ for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
+ // the simple case is if we match, deal with that first.
+ if (c == t->str[t->index]) {
+ if (++t->index == t->len)
+ return t - targets;
+ else
+ continue;
+ }
+
+ // if not we need to walk back and see if we could have matched further
+ // down the stream (ie '1112' doesn't match the first position in '11112'
+ // but it will match the second position so we can't just reset the current
+ // index to 0 when we find a mismatch.
+ if (t->index == 0)
+ continue;
+
+ int origIndex = t->index;
+ do {
+ --t->index;
+ // first check if current char works against the new current index
+ if (c != t->str[t->index])
+ continue;
+
+ // if it's the only char then we're good, nothing more to check
+ if (t->index == 0) {
+ t->index++;
+ break;
+ }
+
+ // otherwise we need to check the rest of the found string
+ int diff = origIndex - t->index;
+ size_t i;
+ for (i = 0; i < t->index; ++i) {
+ if (t->str[i] != t->str[i + diff])
+ break;
+ }
+
+ // if we successfully got through the previous loop then our current
+ // index is good.
+ if (i == t->index) {
+ t->index++;
+ break;
+ }
+
+ // otherwise we just try the next index
+ } while (t->index);
+ }
+ }
+ // unreachable
+ return -1;
+}
+
+// returns the first valid (long) integer value from the current position.
+// initial characters that are not digits (or the minus sign) are skipped
+// function is terminated by the first character that is not a digit.
+long Stream::parseInt()
+{
+ return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout)
+}
+
+// as above but a given skipChar is ignored
+// this allows format characters (typically commas) in values to be ignored
+long Stream::parseInt(char skipChar)
+{
+ bool isNegative = false;
+ long value = 0;
+ int c;
+
+ c = peekNextDigit();
+ // ignore non numeric leading characters
+ if(c < 0) {
+ return 0; // zero returned if timeout
+ }
+
+ do {
+ if(c == skipChar) {
+ } // ignore this charactor
+ else if(c == '-') {
+ isNegative = true;
+ } else if(c >= '0' && c <= '9') { // is c a digit?
+ value = value * 10 + c - '0';
+ }
+ read(); // consume the character we got with peek
+ c = timedPeek();
+ } while((c >= '0' && c <= '9') || c == skipChar);
+
+ if(isNegative) {
+ value = -value;
+ }
+ return value;
+}
+
+// as parseInt but returns a floating point value
+float Stream::parseFloat()
+{
+ return parseFloat(NO_SKIP_CHAR);
+}
+
+// as above but the given skipChar is ignored
+// this allows format characters (typically commas) in values to be ignored
+float Stream::parseFloat(char skipChar)
+{
+ bool isNegative = false;
+ bool isFraction = false;
+ long value = 0;
+ int c;
+ float fraction = 1.0;
+
+ c = peekNextDigit();
+ // ignore non numeric leading characters
+ if(c < 0) {
+ return 0; // zero returned if timeout
+ }
+
+ do {
+ if(c == skipChar) {
+ } // ignore
+ else if(c == '-') {
+ isNegative = true;
+ } else if(c == '.') {
+ isFraction = true;
+ } else if(c >= '0' && c <= '9') { // is c a digit?
+ value = value * 10 + c - '0';
+ if(isFraction) {
+ fraction *= 0.1f;
+ }
+ }
+ read(); // consume the character we got with peek
+ c = timedPeek();
+ } while((c >= '0' && c <= '9') || c == '.' || c == skipChar);
+
+ if(isNegative) {
+ value = -value;
+ }
+ if(isFraction) {
+ return value * fraction;
+ } else {
+ return value;
+ }
+}
+
+// read characters from stream into buffer
+// terminates if length characters have been read, or timeout (see setTimeout)
+// returns the number of characters placed in the buffer
+// the buffer is NOT null terminated.
+//
+size_t Stream::readBytes(char *buffer, size_t length)
+{
+ size_t count = 0;
+ while(count < length) {
+ int c = timedRead();
+ if(c < 0) {
+ break;
+ }
+ *buffer++ = (char) c;
+ count++;
+ }
+ return count;
+}
+
+// as readBytes with terminator character
+// terminates if length characters have been read, timeout, or if the terminator character detected
+// returns the number of characters placed in the buffer (0 means no valid data found)
+
+size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length)
+{
+ if(length < 1) {
+ return 0;
+ }
+ size_t index = 0;
+ while(index < length) {
+ int c = timedRead();
+ if(c < 0 || c == terminator) {
+ break;
+ }
+ *buffer++ = (char) c;
+ index++;
+ }
+ return index; // return number of characters, not including null terminator
+}
+
+std::string Stream::readString()
+{
+ std::string ret;
+ int c = timedRead();
+ while(c >= 0) {
+ ret += (char) c;
+ c = timedRead();
+ }
+ return ret;
+}
+
+std::string Stream::readStringUntil(char terminator)
+{
+ std::string ret;
+ int c = timedRead();
+ while(c >= 0 && c != terminator) {
+ ret += (char) c;
+ c = timedRead();
+ }
+ return ret;
+}
+
+#endif

diff --git a/lib/microReticulum/src/Utilities/Stream.h b/lib/microReticulum/src/Utilities/Stream.h
new file mode 100755
index 0000000..c3c7a70
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/Stream.h
@@ -0,0 +1,143 @@
+/*
+ Stream.h - base class for character-based streams.
+ Copyright (c) 2010 David A. Mellis. All right reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ parsing functions based on TextFinder library by Michael Margolis
+ */
+
+#ifndef ARDUINO
+
+#ifndef Stream_h
+#define Stream_h
+
+#include <inttypes.h>
+#include "Print.h"
+
+// compatability macros for testing
+/*
+ #define getInt() parseInt()
+ #define getInt(skipChar) parseInt(skipchar)
+ #define getFloat() parseFloat()
+ #define getFloat(skipChar) parseFloat(skipChar)
+ #define getString( pre_string, post_string, buffer, length)
+ readBytesBetween( pre_string, terminator, buffer, length)
+ */
+
+class Stream: public Print
+{
+protected:
+ unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read
+ unsigned long _startMillis; // used for timeout measurement
+ int timedRead(); // private method to read stream with timeout
+ int timedPeek(); // private method to peek stream with timeout
+ int peekNextDigit(); // returns the next numeric digit in the stream or -1 if timeout
+
+public:
+ virtual int available() = 0;
+ virtual int read() = 0;
+ virtual int peek() = 0;
+
+ Stream():_startMillis(0)
+ {
+ _timeout = 1000;
+ }
+ virtual ~Stream() {}
+
+// parsing methods
+
+ void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
+ unsigned long getTimeout(void);
+
+ bool find(const char *target); // reads data from the stream until the target string is found
+ bool find(uint8_t *target)
+ {
+ return find((char *) target);
+ }
+ // returns true if target string is found, false if timed out (see setTimeout)
+
+ bool find(const char *target, size_t length); // reads data from the stream until the target string of given length is found
+ bool find(const uint8_t *target, size_t length)
+ {
+ return find((char *) target, length);
+ }
+ // returns true if target string is found, false if timed out
+
+ bool find(char target)
+ {
+ return find (&target, 1);
+ }
+
+ bool findUntil(const char *target, const char *terminator); // as find but search ends if the terminator string is found
+ bool findUntil(const uint8_t *target, const char *terminator)
+ {
+ return findUntil((char *) target, terminator);
+ }
+
+ bool findUntil(const char *target, size_t targetLen, const char *terminate, size_t termLen); // as above but search ends if the terminate string is found
+ bool findUntil(const uint8_t *target, size_t targetLen, const char *terminate, size_t termLen)
+ {
+ return findUntil((char *) target, targetLen, terminate, termLen);
+ }
+
+ long parseInt(); // returns the first valid (long) integer value from the current position.
+ // initial characters that are not digits (or the minus sign) are skipped
+ // integer is terminated by the first character that is not a digit.
+
+ float parseFloat(); // float version of parseInt
+
+ virtual size_t readBytes(char *buffer, size_t length); // read chars from stream into buffer
+ virtual size_t readBytes(uint8_t *buffer, size_t length)
+ {
+ return readBytes((char *) buffer, length);
+ }
+ // terminates if length characters have been read or timeout (see setTimeout)
+ // returns the number of characters placed in the buffer (0 means no valid data found)
+
+ size_t readBytesUntil(char terminator, char *buffer, size_t length); // as readBytes with terminator character
+ size_t readBytesUntil(char terminator, uint8_t *buffer, size_t length)
+ {
+ return readBytesUntil(terminator, (char *) buffer, length);
+ }
+ // terminates if length characters have been read, timeout, or if the terminator character detected
+ // returns the number of characters placed in the buffer (0 means no valid data found)
+
+ // Arduino String functions to be added here
+ virtual std::string readString();
+ std::string readStringUntil(char terminator);
+
+protected:
+ long parseInt(char skipChar); // as above but the given skipChar is ignored
+ // as above but the given skipChar is ignored
+ // this allows format characters (typically commas) in values to be ignored
+
+ float parseFloat(char skipChar); // as above but the given skipChar is ignored
+
+ struct MultiTarget {
+ const char *str; // string you're searching for
+ size_t len; // length of string you're searching for
+ size_t index; // index used by the search routine.
+ };
+
+ // This allows you to search for an arbitrary number of strings.
+ // Returns index of the target that is found first or -1 if timeout occurs.
+ int findMulti(struct MultiTarget *targets, int tCount);
+
+};
+
+#endif
+
+#endif

diff --git a/lib/microReticulum/src/Utilities/tlsf.c b/lib/microReticulum/src/Utilities/tlsf.c
new file mode 100755
index 0000000..b22156e
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/tlsf.c
@@ -0,0 +1,1268 @@
+#if !defined(ESP32)
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tlsf.h"
+
+#if defined(__cplusplus)
+#define tlsf_decl inline
+#else
+#define tlsf_decl static
+#endif
+
+/*
+** Architecture-specific bit manipulation routines.
+**
+** TLSF achieves O(1) cost for malloc and free operations by limiting
+** the search for a free block to a free list of guaranteed size
+** adequate to fulfill the request, combined with efficient free list
+** queries using bitmasks and architecture-specific bit-manipulation
+** routines.
+**
+** Most modern processors provide instructions to count leading zeroes
+** in a word, find the lowest and highest set bit, etc. These
+** specific implementations will be used when available, falling back
+** to a reasonably efficient generic implementation.
+**
+** NOTE: TLSF spec relies on ffs/fls returning value 0..31.
+** ffs/fls return 1-32 by default, returning 0 for error.
+*/
+
+/*
+** Detect whether or not we are building for a 32- or 64-bit (LP/LLP)
+** architecture. There is no reliable portable method at compile-time.
+*/
+#if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) \
+ || defined (_WIN64) || defined (__LP64__) || defined (__LLP64__)
+#define TLSF_64BIT
+#endif
+
+/*
+** gcc 3.4 and above have builtin support, specialized for architecture.
+** Some compilers masquerade as gcc; patchlevel test filters them out.
+*/
+#if defined (__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) \
+ && defined (__GNUC_PATCHLEVEL__)
+
+#if defined (__SNC__)
+/* SNC for Playstation 3. */
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - __builtin_clz(reverse);
+ return bit - 1;
+}
+
+#else
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ return __builtin_ffs(word) - 1;
+}
+
+#endif
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = word ? 32 - __builtin_clz(word) : 0;
+ return bit - 1;
+}
+
+#elif defined (_MSC_VER) && (_MSC_VER >= 1400) && (defined (_M_IX86) || defined (_M_X64))
+/* Microsoft Visual C++ support on x86/X64 architectures. */
+
+#include <intrin.h>
+
+#pragma intrinsic(_BitScanReverse)
+#pragma intrinsic(_BitScanForward)
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ unsigned long index;
+ return _BitScanReverse(&index, word) ? index : -1;
+}
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ unsigned long index;
+ return _BitScanForward(&index, word) ? index : -1;
+}
+
+#elif defined (_MSC_VER) && defined (_M_PPC)
+/* Microsoft Visual C++ support on PowerPC architectures. */
+
+#include <ppcintrinsics.h>
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = 32 - _CountLeadingZeros(word);
+ return bit - 1;
+}
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - _CountLeadingZeros(reverse);
+ return bit - 1;
+}
+
+#elif defined (__ARMCC_VERSION)
+/* RealView Compilation Tools for ARM */
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - __clz(reverse);
+ return bit - 1;
+}
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = word ? 32 - __clz(word) : 0;
+ return bit - 1;
+}
+
+#elif defined (__ghs__)
+/* Green Hills support for PowerPC */
+
+#include <ppc_ghs.h>
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - __CLZ32(reverse);
+ return bit - 1;
+}
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = word ? 32 - __CLZ32(word) : 0;
+ return bit - 1;
+}
+
+#else
+/* Fall back to generic implementation. */
+
+tlsf_decl int tlsf_fls_generic(unsigned int word)
+{
+ int bit = 32;
+
+ if (!word) bit -= 1;
+ if (!(word & 0xffff0000)) { word <<= 16; bit -= 16; }
+ if (!(word & 0xff000000)) { word <<= 8; bit -= 8; }
+ if (!(word & 0xf0000000)) { word <<= 4; bit -= 4; }
+ if (!(word & 0xc0000000)) { word <<= 2; bit -= 2; }
+ if (!(word & 0x80000000)) { word <<= 1; bit -= 1; }
+
+ return bit;
+}
+
+/* Implement ffs in terms of fls. */
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ return tlsf_fls_generic(word & (~word + 1)) - 1;
+}
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ return tlsf_fls_generic(word) - 1;
+}
+
+#endif
+
+/* Possibly 64-bit version of tlsf_fls. */
+#if defined (TLSF_64BIT)
+tlsf_decl int tlsf_fls_sizet(size_t size)
+{
+ int high = (int)(size >> 32);
+ int bits = 0;
+ if (high)
+ {
+ bits = 32 + tlsf_fls(high);
+ }
+ else
+ {
+ bits = tlsf_fls((int)size & 0xffffffff);
+
+ }
+ return bits;
+}
+#else
+#define tlsf_fls_sizet tlsf_fls
+#endif
+
+#undef tlsf_decl
+
+/*
+** Constants.
+*/
+
+/* Public constants: may be modified. */
+enum tlsf_public
+{
+ /* log2 of number of linear subdivisions of block sizes. Larger
+ ** values require more memory in the control structure. Values of
+ ** 4 or 5 are typical.
+ */
+ SL_INDEX_COUNT_LOG2 = 5,
+};
+
+/* Private constants: do not modify. */
+enum tlsf_private
+{
+#if defined (TLSF_64BIT)
+ /* All allocation sizes and addresses are aligned to 8 bytes. */
+ ALIGN_SIZE_LOG2 = 3,
+#else
+ /* All allocation sizes and addresses are aligned to 4 bytes. */
+ ALIGN_SIZE_LOG2 = 2,
+#endif
+ ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2),
+
+ /*
+ ** We support allocations of sizes up to (1 << FL_INDEX_MAX) bits.
+ ** However, because we linearly subdivide the second-level lists, and
+ ** our minimum size granularity is 4 bytes, it doesn't make sense to
+ ** create first-level lists for sizes smaller than SL_INDEX_COUNT * 4,
+ ** or (1 << (SL_INDEX_COUNT_LOG2 + 2)) bytes, as there we will be
+ ** trying to split size ranges into more slots than we have available.
+ ** Instead, we calculate the minimum threshold size, and place all
+ ** blocks below that size into the 0th first-level list.
+ */
+
+#if defined (TLSF_64BIT)
+ /*
+ ** TODO: We can increase this to support larger sizes, at the expense
+ ** of more overhead in the TLSF structure.
+ */
+ FL_INDEX_MAX = 32,
+#else
+ FL_INDEX_MAX = 30,
+#endif
+ SL_INDEX_COUNT = (1 << SL_INDEX_COUNT_LOG2),
+ FL_INDEX_SHIFT = (SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2),
+ FL_INDEX_COUNT = (FL_INDEX_MAX - FL_INDEX_SHIFT + 1),
+
+ SMALL_BLOCK_SIZE = (1 << FL_INDEX_SHIFT),
+};
+
+/*
+** Cast and min/max macros.
+*/
+
+#define tlsf_cast(t, exp) ((t) (exp))
+#define tlsf_min(a, b) ((a) < (b) ? (a) : (b))
+#define tlsf_max(a, b) ((a) > (b) ? (a) : (b))
+
+/*
+** Set assert macro, if it has not been provided by the user.
+*/
+#if !defined (tlsf_assert)
+#define tlsf_assert assert
+#endif
+
+/*
+** Static assertion mechanism.
+*/
+
+#define _tlsf_glue2(x, y) x ## y
+#define _tlsf_glue(x, y) _tlsf_glue2(x, y)
+#define tlsf_static_assert(exp) \
+ typedef char _tlsf_glue(static_assert, __LINE__) [(exp) ? 1 : -1]
+
+/* This code has been tested on 32- and 64-bit (LP/LLP) architectures. */
+tlsf_static_assert(sizeof(int) * CHAR_BIT == 32);
+tlsf_static_assert(sizeof(size_t) * CHAR_BIT >= 32);
+tlsf_static_assert(sizeof(size_t) * CHAR_BIT <= 64);
+
+/* SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type. */
+tlsf_static_assert(sizeof(unsigned int) * CHAR_BIT >= SL_INDEX_COUNT);
+
+/* Ensure we've properly tuned our sizes. */
+tlsf_static_assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
+
+/*
+** Data structures and associated constants.
+*/
+
+/*
+** Block header structure.
+**
+** There are several implementation subtleties involved:
+** - The prev_phys_block field is only valid if the previous block is free.
+** - The prev_phys_block field is actually stored at the end of the
+** previous block. It appears at the beginning of this structure only to
+** simplify the implementation.
+** - The next_free / prev_free fields are only valid if the block is free.
+*/
+typedef struct block_header_t
+{
+ /* Points to the previous physical block. */
+ struct block_header_t* prev_phys_block;
+
+ /* The size of this block, excluding the block header. */
+ size_t size;
+
+ /* Next and previous free blocks. */
+ struct block_header_t* next_free;
+ struct block_header_t* prev_free;
+} block_header_t;
+
+/*
+** Since block sizes are always at least a multiple of 4, the two least
+** significant bits of the size field are used to store the block status:
+** - bit 0: whether block is busy or free
+** - bit 1: whether previous block is busy or free
+*/
+static const size_t block_header_free_bit = 1 << 0;
+static const size_t block_header_prev_free_bit = 1 << 1;
+
+/*
+** The size of the block header exposed to used blocks is the size field.
+** The prev_phys_block field is stored *inside* the previous free block.
+*/
+static const size_t block_header_overhead = sizeof(size_t);
+
+/* User data starts directly after the size field in a used block. */
+static const size_t block_start_offset =
+ offsetof(block_header_t, size) + sizeof(size_t);
+
+/*
+** A free block must be large enough to store its header minus the size of
+** the prev_phys_block field, and no larger than the number of addressable
+** bits for FL_INDEX.
+*/
+static const size_t block_size_min =
+ sizeof(block_header_t) - sizeof(block_header_t*);
+static const size_t block_size_max = tlsf_cast(size_t, 1) << FL_INDEX_MAX;
+
+
+/* The TLSF control structure. */
+typedef struct control_t
+{
+ /* Empty lists point at this block to indicate they are free. */
+ block_header_t block_null;
+
+ /* Bitmaps for free lists. */
+ unsigned int fl_bitmap;
+ unsigned int sl_bitmap[FL_INDEX_COUNT];
+
+ /* Head of free lists. */
+ block_header_t* blocks[FL_INDEX_COUNT][SL_INDEX_COUNT];
+} control_t;
+
+/* A type used for casting when doing pointer arithmetic. */
+typedef ptrdiff_t tlsfptr_t;
+
+/*
+** block_header_t member functions.
+*/
+
+static size_t block_size(const block_header_t* block)
+{
+ return block->size & ~(block_header_free_bit | block_header_prev_free_bit);
+}
+
+static void block_set_size(block_header_t* block, size_t size)
+{
+ const size_t oldsize = block->size;
+ block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit));
+}
+
+static int block_is_last(const block_header_t* block)
+{
+ return block_size(block) == 0;
+}
+
+static int block_is_free(const block_header_t* block)
+{
+ return tlsf_cast(int, block->size & block_header_free_bit);
+}
+
+static void block_set_free(block_header_t* block)
+{
+ block->size |= block_header_free_bit;
+}
+
+static void block_set_used(block_header_t* block)
+{
+ block->size &= ~block_header_free_bit;
+}
+
+static int block_is_prev_free(const block_header_t* block)
+{
+ return tlsf_cast(int, block->size & block_header_prev_free_bit);
+}
+
+static void block_set_prev_free(block_header_t* block)
+{
+ block->size |= block_header_prev_free_bit;
+}
+
+static void block_set_prev_used(block_header_t* block)
+{
+ block->size &= ~block_header_prev_free_bit;
+}
+
+static block_header_t* block_from_ptr(const void* ptr)
+{
+ return tlsf_cast(block_header_t*,
+ tlsf_cast(unsigned char*, ptr) - block_start_offset);
+}
+
+static void* block_to_ptr(const block_header_t* block)
+{
+ return tlsf_cast(void*,
+ tlsf_cast(unsigned char*, block) + block_start_offset);
+}
+
+/* Return location of next block after block of given size. */
+static block_header_t* offset_to_block(const void* ptr, size_t size)
+{
+ return tlsf_cast(block_header_t*, tlsf_cast(tlsfptr_t, ptr) + size);
+}
+
+/* Return location of previous block. */
+static block_header_t* block_prev(const block_header_t* block)
+{
+ tlsf_assert(block_is_prev_free(block) && "previous block must be free");
+ return block->prev_phys_block;
+}
+
+/* Return location of next existing block. */
+static block_header_t* block_next(const block_header_t* block)
+{
+ block_header_t* next = offset_to_block(block_to_ptr(block),
+ block_size(block) - block_header_overhead);
+ tlsf_assert(!block_is_last(block));
+ return next;
+}
+
+/* Link a new block with its physical neighbor, return the neighbor. */
+static block_header_t* block_link_next(block_header_t* block)
+{
+ block_header_t* next = block_next(block);
+ next->prev_phys_block = block;
+ return next;
+}
+
+static void block_mark_as_free(block_header_t* block)
+{
+ /* Link the block to the next block, first. */
+ block_header_t* next = block_link_next(block);
+ block_set_prev_free(next);
+ block_set_free(block);
+}
+
+static void block_mark_as_used(block_header_t* block)
+{
+ block_header_t* next = block_next(block);
+ block_set_prev_used(next);
+ block_set_used(block);
+}
+
+static size_t align_up(size_t x, size_t align)
+{
+ tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
+ return (x + (align - 1)) & ~(align - 1);
+}
+
+static size_t align_down(size_t x, size_t align)
+{
+ tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
+ return x - (x & (align - 1));
+}
+
+static void* align_ptr(const void* ptr, size_t align)
+{
+ const tlsfptr_t aligned =
+ (tlsf_cast(tlsfptr_t, ptr) + (align - 1)) & ~(align - 1);
+ tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
+ return tlsf_cast(void*, aligned);
+}
+
+/*
+** Adjust an allocation size to be aligned to word size, and no smaller
+** than internal minimum.
+*/
+static size_t adjust_request_size(size_t size, size_t align)
+{
+ size_t adjust = 0;
+ if (size)
+ {
+ const size_t aligned = align_up(size, align);
+
+ /* aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap */
+ if (aligned < block_size_max)
+ {
+ adjust = tlsf_max(aligned, block_size_min);
+ }
+ }
+ return adjust;
+}
+
+/*
+** TLSF utility functions. In most cases, these are direct translations of
+** the documentation found in the white paper.
+*/
+
+static void mapping_insert(size_t size, int* fli, int* sli)
+{
+ int fl, sl;
+ if (size < SMALL_BLOCK_SIZE)
+ {
+ /* Store small blocks in first list. */
+ fl = 0;
+ sl = tlsf_cast(int, size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
+ }
+ else
+ {
+ fl = tlsf_fls_sizet(size);
+ sl = tlsf_cast(int, size >> (fl - SL_INDEX_COUNT_LOG2)) ^ (1 << SL_INDEX_COUNT_LOG2);
+ fl -= (FL_INDEX_SHIFT - 1);
+ }
+ *fli = fl;
+ *sli = sl;
+}
+
+/* This version rounds up to the next block size (for allocations) */
+static void mapping_search(size_t size, int* fli, int* sli)
+{
+ if (size >= SMALL_BLOCK_SIZE)
+ {
+ const size_t round = (1 << (tlsf_fls_sizet(size) - SL_INDEX_COUNT_LOG2)) - 1;
+ size += round;
+ }
+ mapping_insert(size, fli, sli);
+}
+
+static block_header_t* search_suitable_block(control_t* control, int* fli, int* sli)
+{
+ int fl = *fli;
+ int sl = *sli;
+
+ /*
+ ** First, search for a block in the list associated with the given
+ ** fl/sl index.
+ */
+ unsigned int sl_map = control->sl_bitmap[fl] & (~0U << sl);
+ if (!sl_map)
+ {
+ /* No block exists. Search in the next largest first-level list. */
+ const unsigned int fl_map = control->fl_bitmap & (~0U << (fl + 1));
+ if (!fl_map)
+ {
+ /* No free blocks available, memory has been exhausted. */
+ return 0;
+ }
+
+ fl = tlsf_ffs(fl_map);
+ *fli = fl;
+ sl_map = control->sl_bitmap[fl];
+ }
+ tlsf_assert(sl_map && "internal error - second level bitmap is null");
+ sl = tlsf_ffs(sl_map);
+ *sli = sl;
+
+ /* Return the first block in the free list. */
+ return control->blocks[fl][sl];
+}
+
+/* Remove a free block from the free list.*/
+static void remove_free_block(control_t* control, block_header_t* block, int fl, int sl)
+{
+ block_header_t* prev = block->prev_free;
+ block_header_t* next = block->next_free;
+ tlsf_assert(prev && "prev_free field can not be null");
+ tlsf_assert(next && "next_free field can not be null");
+ next->prev_free = prev;
+ prev->next_free = next;
+
+ /* If this block is the head of the free list, set new head. */
+ if (control->blocks[fl][sl] == block)
+ {
+ control->blocks[fl][sl] = next;
+
+ /* If the new head is null, clear the bitmap. */
+ if (next == &control->block_null)
+ {
+ control->sl_bitmap[fl] &= ~(1U << sl);
+
+ /* If the second bitmap is now empty, clear the fl bitmap. */
+ if (!control->sl_bitmap[fl])
+ {
+ control->fl_bitmap &= ~(1U << fl);
+ }
+ }
+ }
+}
+
+/* Insert a free block into the free block list. */
+static void insert_free_block(control_t* control, block_header_t* block, int fl, int sl)
+{
+ block_header_t* current = control->blocks[fl][sl];
+ tlsf_assert(current && "free list cannot have a null entry");
+ tlsf_assert(block && "cannot insert a null entry into the free list");
+ block->next_free = current;
+ block->prev_free = &control->block_null;
+ current->prev_free = block;
+
+ tlsf_assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE)
+ && "block not aligned properly");
+ /*
+ ** Insert the new block at the head of the list, and mark the first-
+ ** and second-level bitmaps appropriately.
+ */
+ control->blocks[fl][sl] = block;
+ control->fl_bitmap |= (1U << fl);
+ control->sl_bitmap[fl] |= (1U << sl);
+}
+
+/* Remove a given block from the free list. */
+static void block_remove(control_t* control, block_header_t* block)
+{
+ int fl, sl;
+ mapping_insert(block_size(block), &fl, &sl);
+ remove_free_block(control, block, fl, sl);
+}
+
+/* Insert a given block into the free list. */
+static void block_insert(control_t* control, block_header_t* block)
+{
+ int fl, sl;
+ mapping_insert(block_size(block), &fl, &sl);
+ insert_free_block(control, block, fl, sl);
+}
+
+static int block_can_split(block_header_t* block, size_t size)
+{
+ return block_size(block) >= sizeof(block_header_t) + size;
+}
+
+/* Split a block into two, the second of which is free. */
+static block_header_t* block_split(block_header_t* block, size_t size)
+{
+ /* Calculate the amount of space left in the remaining block. */
+ block_header_t* remaining =
+ offset_to_block(block_to_ptr(block), size - block_header_overhead);
+
+ const size_t remain_size = block_size(block) - (size + block_header_overhead);
+
+ tlsf_assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE)
+ && "remaining block not aligned properly");
+
+ tlsf_assert(block_size(block) == remain_size + size + block_header_overhead);
+ block_set_size(remaining, remain_size);
+ tlsf_assert(block_size(remaining) >= block_size_min && "block split with invalid size");
+
+ block_set_size(block, size);
+ block_mark_as_free(remaining);
+
+ return remaining;
+}
+
+/* Absorb a free block's storage into an adjacent previous free block. */
+static block_header_t* block_absorb(block_header_t* prev, block_header_t* block)
+{
+ tlsf_assert(!block_is_last(prev) && "previous block can't be last");
+ /* Note: Leaves flags untouched. */
+ prev->size += block_size(block) + block_header_overhead;
+ block_link_next(prev);
+ return prev;
+}
+
+/* Merge a just-freed block with an adjacent previous free block. */
+static block_header_t* block_merge_prev(control_t* control, block_header_t* block)
+{
+ if (block_is_prev_free(block))
+ {
+ block_header_t* prev = block_prev(block);
+ tlsf_assert(prev && "prev physical block can't be null");
+ tlsf_assert(block_is_free(prev) && "prev block is not free though marked as such");
+ block_remove(control, prev);
+ block = block_absorb(prev, block);
+ }
+
+ return block;
+}
+
+/* Merge a just-freed block with an adjacent free block. */
+static block_header_t* block_merge_next(control_t* control, block_header_t* block)
+{
+ block_header_t* next = block_next(block);
+ tlsf_assert(next && "next physical block can't be null");
+
+ if (block_is_free(next))
+ {
+ tlsf_assert(!block_is_last(block) && "previous block can't be last");
+ block_remove(control, next);
+ block = block_absorb(block, next);
+ }
+
+ return block;
+}
+
+/* Trim any trailing block space off the end of a block, return to pool. */
+static void block_trim_free(control_t* control, block_header_t* block, size_t size)
+{
+ tlsf_assert(block_is_free(block) && "block must be free");
+ if (block_can_split(block, size))
+ {
+ block_header_t* remaining_block = block_split(block, size);
+ block_link_next(block);
+ block_set_prev_free(remaining_block);
+ block_insert(control, remaining_block);
+ }
+}
+
+/* Trim any trailing block space off the end of a used block, return to pool. */
+static void block_trim_used(control_t* control, block_header_t* block, size_t size)
+{
+ tlsf_assert(!block_is_free(block) && "block must be used");
+ if (block_can_split(block, size))
+ {
+ /* If the next block is free, we must coalesce. */
+ block_header_t* remaining_block = block_split(block, size);
+ block_set_prev_used(remaining_block);
+
+ remaining_block = block_merge_next(control, remaining_block);
+ block_insert(control, remaining_block);
+ }
+}
+
+static block_header_t* block_trim_free_leading(control_t* control, block_header_t* block, size_t size)
+{
+ block_header_t* remaining_block = block;
+ if (block_can_split(block, size))
+ {
+ /* We want the 2nd block. */
+ remaining_block = block_split(block, size - block_header_overhead);
+ block_set_prev_free(remaining_block);
+
+ block_link_next(block);
+ block_insert(control, block);
+ }
+
+ return remaining_block;
+}
+
+static block_header_t* block_locate_free(control_t* control, size_t size)
+{
+ int fl = 0, sl = 0;
+ block_header_t* block = 0;
+
+ if (size)
+ {
+ mapping_search(size, &fl, &sl);
+
+ /*
+ ** mapping_search can futz with the size, so for excessively large sizes it can sometimes wind up
+ ** with indices that are off the end of the block array.
+ ** So, we protect against that here, since this is the only callsite of mapping_search.
+ ** Note that we don't need to check sl, since it comes from a modulo operation that guarantees it's always in range.
+ */
+ if (fl < FL_INDEX_COUNT)
+ {
+ block = search_suitable_block(control, &fl, &sl);
+ }
+ }
+
+ if (block)
+ {
+ tlsf_assert(block_size(block) >= size);
+ remove_free_block(control, block, fl, sl);
+ }
+
+ return block;
+}
+
+static void* block_prepare_used(control_t* control, block_header_t* block, size_t size)
+{
+ void* p = 0;
+ if (block)
+ {
+ tlsf_assert(size && "size must be non-zero");
+ block_trim_free(control, block, size);
+ block_mark_as_used(block);
+ p = block_to_ptr(block);
+ }
+ return p;
+}
+
+/* Clear structure and point all empty lists at the null block. */
+static void control_construct(control_t* control)
+{
+ int i, j;
+
+ control->block_null.next_free = &control->block_null;
+ control->block_null.prev_free = &control->block_null;
+
+ control->fl_bitmap = 0;
+ for (i = 0; i < FL_INDEX_COUNT; ++i)
+ {
+ control->sl_bitmap[i] = 0;
+ for (j = 0; j < SL_INDEX_COUNT; ++j)
+ {
+ control->blocks[i][j] = &control->block_null;
+ }
+ }
+}
+
+/*
+** Debugging utilities.
+*/
+
+typedef struct integrity_t
+{
+ int prev_status;
+ int status;
+} integrity_t;
+
+#define tlsf_insist(x) { tlsf_assert(x); if (!(x)) { status--; } }
+
+static void integrity_walker(void* ptr, size_t size, int used, void* user)
+{
+ block_header_t* block = block_from_ptr(ptr);
+ integrity_t* integ = tlsf_cast(integrity_t*, user);
+ const int this_prev_status = block_is_prev_free(block) ? 1 : 0;
+ const int this_status = block_is_free(block) ? 1 : 0;
+ const size_t this_block_size = block_size(block);
+
+ int status = 0;
+ (void)used;
+ tlsf_insist(integ->prev_status == this_prev_status && "prev status incorrect");
+ tlsf_insist(size == this_block_size && "block size incorrect");
+
+ integ->prev_status = this_status;
+ integ->status += status;
+}
+
+int tlsf_check(tlsf_t tlsf)
+{
+ int i, j;
+
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ int status = 0;
+
+ /* Check that the free lists and bitmaps are accurate. */
+ for (i = 0; i < FL_INDEX_COUNT; ++i)
+ {
+ for (j = 0; j < SL_INDEX_COUNT; ++j)
+ {
+ const int fl_map = control->fl_bitmap & (1U << i);
+ const int sl_list = control->sl_bitmap[i];
+ const int sl_map = sl_list & (1U << j);
+ const block_header_t* block = control->blocks[i][j];
+
+ /* Check that first- and second-level lists agree. */
+ if (!fl_map)
+ {
+ tlsf_insist(!sl_map && "second-level map must be null");
+ }
+
+ if (!sl_map)
+ {
+ tlsf_insist(block == &control->block_null && "block list must be null");
+ continue;
+ }
+
+ /* Check that there is at least one free block. */
+ tlsf_insist(sl_list && "no free blocks in second-level map");
+ tlsf_insist(block != &control->block_null && "block should not be null");
+
+ while (block != &control->block_null)
+ {
+ int fli, sli;
+ tlsf_insist(block_is_free(block) && "block should be free");
+ tlsf_insist(!block_is_prev_free(block) && "blocks should have coalesced");
+ tlsf_insist(!block_is_free(block_next(block)) && "blocks should have coalesced");
+ tlsf_insist(block_is_prev_free(block_next(block)) && "block should be free");
+ tlsf_insist(block_size(block) >= block_size_min && "block not minimum size");
+
+ mapping_insert(block_size(block), &fli, &sli);
+ tlsf_insist(fli == i && sli == j && "block size indexed in wrong list");
+ block = block->next_free;
+ }
+ }
+ }
+
+ return status;
+}
+
+#undef tlsf_insist
+
+static void default_walker(void* ptr, size_t size, int used, void* user)
+{
+ (void)user;
+ printf("\t%p %s size: %x (%p)\n", ptr, used ? "used" : "free", (unsigned int)size, block_from_ptr(ptr));
+}
+
+void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user)
+{
+ tlsf_walker pool_walker = walker ? walker : default_walker;
+ block_header_t* block =
+ offset_to_block(pool, -(int)block_header_overhead);
+
+ while (block && !block_is_last(block))
+ {
+ pool_walker(
+ block_to_ptr(block),
+ block_size(block),
+ !block_is_free(block),
+ user);
+ block = block_next(block);
+ }
+}
+
+size_t tlsf_block_size(void* ptr)
+{
+ size_t size = 0;
+ if (ptr)
+ {
+ const block_header_t* block = block_from_ptr(ptr);
+ size = block_size(block);
+ }
+ return size;
+}
+
+int tlsf_check_pool(pool_t pool)
+{
+ /* Check that the blocks are physically correct. */
+ integrity_t integ = { 0, 0 };
+ tlsf_walk_pool(pool, integrity_walker, &integ);
+
+ return integ.status;
+}
+
+/*
+** Size of the TLSF structures in a given memory block passed to
+** tlsf_create, equal to the size of a control_t
+*/
+size_t tlsf_size(void)
+{
+ return sizeof(control_t);
+}
+
+size_t tlsf_align_size(void)
+{
+ return ALIGN_SIZE;
+}
+
+size_t tlsf_block_size_min(void)
+{
+ return block_size_min;
+}
+
+size_t tlsf_block_size_max(void)
+{
+ return block_size_max;
+}
+
+/*
+** Overhead of the TLSF structures in a given memory block passed to
+** tlsf_add_pool, equal to the overhead of a free block and the
+** sentinel block.
+*/
+size_t tlsf_pool_overhead(void)
+{
+ return 2 * block_header_overhead;
+}
+
+size_t tlsf_alloc_overhead(void)
+{
+ return block_header_overhead;
+}
+
+pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes)
+{
+ block_header_t* block;
+ block_header_t* next;
+
+ const size_t pool_overhead = tlsf_pool_overhead();
+ const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
+
+ if (((ptrdiff_t)mem % ALIGN_SIZE) != 0)
+ {
+ printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n",
+ (unsigned int)ALIGN_SIZE);
+ return 0;
+ }
+
+ if (pool_bytes < block_size_min || pool_bytes > block_size_max)
+ {
+#if defined (TLSF_64BIT)
+ printf("tlsf_add_pool: Memory size must be between 0x%x and 0x%x00 bytes.\n",
+ (unsigned int)(pool_overhead + block_size_min),
+ (unsigned int)((pool_overhead + block_size_max) / 256));
+#else
+ printf("tlsf_add_pool: Memory size must be between %u and %u bytes.\n",
+ (unsigned int)(pool_overhead + block_size_min),
+ (unsigned int)(pool_overhead + block_size_max));
+#endif
+ return 0;
+ }
+
+ /*
+ ** Create the main free block. Offset the start of the block slightly
+ ** so that the prev_phys_block field falls outside of the pool -
+ ** it will never be used.
+ */
+ block = offset_to_block(mem, -(tlsfptr_t)block_header_overhead);
+ block_set_size(block, pool_bytes);
+ block_set_free(block);
+ block_set_prev_used(block);
+ block_insert(tlsf_cast(control_t*, tlsf), block);
+
+ /* Split the block to create a zero-size sentinel block. */
+ next = block_link_next(block);
+ block_set_size(next, 0);
+ block_set_used(next);
+ block_set_prev_free(next);
+
+ return mem;
+}
+
+void tlsf_remove_pool(tlsf_t tlsf, pool_t pool)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ block_header_t* block = offset_to_block(pool, -(int)block_header_overhead);
+
+ int fl = 0, sl = 0;
+
+ tlsf_assert(block_is_free(block) && "block should be free");
+ tlsf_assert(!block_is_free(block_next(block)) && "next block should not be free");
+ tlsf_assert(block_size(block_next(block)) == 0 && "next block size should be zero");
+
+ mapping_insert(block_size(block), &fl, &sl);
+ remove_free_block(control, block, fl, sl);
+}
+
+/*
+** TLSF main interface.
+*/
+
+#if _DEBUG
+int test_ffs_fls()
+{
+ /* Verify ffs/fls work properly. */
+ int rv = 0;
+ rv += (tlsf_ffs(0) == -1) ? 0 : 0x1;
+ rv += (tlsf_fls(0) == -1) ? 0 : 0x2;
+ rv += (tlsf_ffs(1) == 0) ? 0 : 0x4;
+ rv += (tlsf_fls(1) == 0) ? 0 : 0x8;
+ rv += (tlsf_ffs(0x80000000) == 31) ? 0 : 0x10;
+ rv += (tlsf_ffs(0x80008000) == 15) ? 0 : 0x20;
+ rv += (tlsf_fls(0x80000008) == 31) ? 0 : 0x40;
+ rv += (tlsf_fls(0x7FFFFFFF) == 30) ? 0 : 0x80;
+
+#if defined (TLSF_64BIT)
+ rv += (tlsf_fls_sizet(0x80000000) == 31) ? 0 : 0x100;
+ rv += (tlsf_fls_sizet(0x100000000) == 32) ? 0 : 0x200;
+ rv += (tlsf_fls_sizet(0xffffffffffffffff) == 63) ? 0 : 0x400;
+#endif
+
+ if (rv)
+ {
+ printf("test_ffs_fls: %x ffs/fls tests failed.\n", rv);
+ }
+ return rv;
+}
+#endif
+
+tlsf_t tlsf_create(void* mem)
+{
+#if _DEBUG
+ if (test_ffs_fls())
+ {
+ return 0;
+ }
+#endif
+
+ if (((tlsfptr_t)mem % ALIGN_SIZE) != 0)
+ {
+ printf("tlsf_create: Memory must be aligned to %u bytes.\n",
+ (unsigned int)ALIGN_SIZE);
+ return 0;
+ }
+
+ control_construct(tlsf_cast(control_t*, mem));
+
+ return tlsf_cast(tlsf_t, mem);
+}
+
+tlsf_t tlsf_create_with_pool(void* mem, size_t bytes)
+{
+ tlsf_t tlsf = tlsf_create(mem);
+ tlsf_add_pool(tlsf, (char*)mem + tlsf_size(), bytes - tlsf_size());
+ return tlsf;
+}
+
+void tlsf_destroy(tlsf_t tlsf)
+{
+ /* Nothing to do. */
+ (void)tlsf;
+}
+
+pool_t tlsf_get_pool(tlsf_t tlsf)
+{
+ return tlsf_cast(pool_t, (char*)tlsf + tlsf_size());
+}
+
+void* tlsf_malloc(tlsf_t tlsf, size_t size)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ const size_t adjust = adjust_request_size(size, ALIGN_SIZE);
+ block_header_t* block = block_locate_free(control, adjust);
+ return block_prepare_used(control, block, adjust);
+}
+
+void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ const size_t adjust = adjust_request_size(size, ALIGN_SIZE);
+
+ /*
+ ** We must allocate an additional minimum block size bytes so that if
+ ** our free block will leave an alignment gap which is smaller, we can
+ ** trim a leading free block and release it back to the pool. We must
+ ** do this because the previous physical block is in use, therefore
+ ** the prev_phys_block field is not valid, and we can't simply adjust
+ ** the size of that block.
+ */
+ const size_t gap_minimum = sizeof(block_header_t);
+ const size_t size_with_gap = adjust_request_size(adjust + align + gap_minimum, align);
+
+ /*
+ ** If alignment is less than or equals base alignment, we're done.
+ ** If we requested 0 bytes, return null, as tlsf_malloc(0) does.
+ */
+ const size_t aligned_size = (adjust && align > ALIGN_SIZE) ? size_with_gap : adjust;
+
+ block_header_t* block = block_locate_free(control, aligned_size);
+
+ /* This can't be a static assert. */
+ tlsf_assert(sizeof(block_header_t) == block_size_min + block_header_overhead);
+
+ if (block)
+ {
+ void* ptr = block_to_ptr(block);
+ void* aligned = align_ptr(ptr, align);
+ size_t gap = tlsf_cast(size_t,
+ tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr));
+
+ /* If gap size is too small, offset to next aligned boundary. */
+ if (gap && gap < gap_minimum)
+ {
+ const size_t gap_remain = gap_minimum - gap;
+ const size_t offset = tlsf_max(gap_remain, align);
+ const void* next_aligned = tlsf_cast(void*,
+ tlsf_cast(tlsfptr_t, aligned) + offset);
+
+ aligned = align_ptr(next_aligned, align);
+ gap = tlsf_cast(size_t,
+ tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr));
+ }
+
+ if (gap)
+ {
+ tlsf_assert(gap >= gap_minimum && "gap size too small");
+ block = block_trim_free_leading(control, block, gap);
+ }
+ }
+
+ return block_prepare_used(control, block, adjust);
+}
+
+void tlsf_free(tlsf_t tlsf, void* ptr)
+{
+ /* Don't attempt to free a NULL pointer. */
+ if (ptr)
+ {
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ block_header_t* block = block_from_ptr(ptr);
+ tlsf_assert(!block_is_free(block) && "block already marked as free");
+ block_mark_as_free(block);
+ block = block_merge_prev(control, block);
+ block = block_merge_next(control, block);
+ block_insert(control, block);
+ }
+}
+
+/*
+** The TLSF block information provides us with enough information to
+** provide a reasonably intelligent implementation of realloc, growing or
+** shrinking the currently allocated block as required.
+**
+** This routine handles the somewhat esoteric edge cases of realloc:
+** - a non-zero size with a null pointer will behave like malloc
+** - a zero size with a non-null pointer will behave like free
+** - a request that cannot be satisfied will leave the original buffer
+** untouched
+** - an extended buffer size will leave the newly-allocated area with
+** contents undefined
+*/
+void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ void* p = 0;
+
+ /* Zero-size requests are treated as free. */
+ if (ptr && size == 0)
+ {
+ tlsf_free(tlsf, ptr);
+ }
+ /* Requests with NULL pointers are treated as malloc. */
+ else if (!ptr)
+ {
+ p = tlsf_malloc(tlsf, size);
+ }
+ else
+ {
+ block_header_t* block = block_from_ptr(ptr);
+ block_header_t* next = block_next(block);
+
+ const size_t cursize = block_size(block);
+ const size_t combined = cursize + block_size(next) + block_header_overhead;
+ const size_t adjust = adjust_request_size(size, ALIGN_SIZE);
+
+ tlsf_assert(!block_is_free(block) && "block already marked as free");
+
+ /*
+ ** If the next block is used, or when combined with the current
+ ** block, does not offer enough space, we must reallocate and copy.
+ */
+ if (adjust > cursize && (!block_is_free(next) || adjust > combined))
+ {
+ p = tlsf_malloc(tlsf, size);
+ if (p)
+ {
+ const size_t minsize = tlsf_min(cursize, size);
+ memcpy(p, ptr, minsize);
+ tlsf_free(tlsf, ptr);
+ }
+ }
+ else
+ {
+ /* Do we need to expand to the next block? */
+ if (adjust > cursize)
+ {
+ block_merge_next(control, block);
+ block_mark_as_used(block);
+ }
+
+ /* Trim the resulting block and return the original pointer. */
+ block_trim_used(control, block, adjust);
+ p = ptr;
+ }
+ }
+
+ return p;
+}
+
+#endif

diff --git a/lib/microReticulum/src/Utilities/tlsf.h b/lib/microReticulum/src/Utilities/tlsf.h
new file mode 100755
index 0000000..e9b5a91
--- /dev/null
+++ b/lib/microReticulum/src/Utilities/tlsf.h
@@ -0,0 +1,90 @@
+#ifndef INCLUDED_tlsf
+#define INCLUDED_tlsf
+
+/*
+** Two Level Segregated Fit memory allocator, version 3.1.
+** Written by Matthew Conte
+** http://tlsf.baisoku.org
+**
+** Based on the original documentation by Miguel Masmano:
+** http://www.gii.upv.es/tlsf/main/docs
+**
+** This implementation was written to the specification
+** of the document, therefore no GPL restrictions apply.
+**
+** Copyright (c) 2006-2016, Matthew Conte
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of the copyright holder nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
+** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <stddef.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */
+/* pool_t: a block of memory that TLSF can manage. */
+typedef void* tlsf_t;
+typedef void* pool_t;
+
+/* Create/destroy a memory pool. */
+tlsf_t tlsf_create(void* mem);
+tlsf_t tlsf_create_with_pool(void* mem, size_t bytes);
+void tlsf_destroy(tlsf_t tlsf);
+pool_t tlsf_get_pool(tlsf_t tlsf);
+
+/* Add/remove memory pools. */
+pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes);
+void tlsf_remove_pool(tlsf_t tlsf, pool_t pool);
+
+/* malloc/memalign/realloc/free replacements. */
+void* tlsf_malloc(tlsf_t tlsf, size_t bytes);
+void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t bytes);
+void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size);
+void tlsf_free(tlsf_t tlsf, void* ptr);
+
+/* Returns internal block size, not original request size */
+size_t tlsf_block_size(void* ptr);
+
+/* Overheads/limits of internal structures. */
+size_t tlsf_size(void);
+size_t tlsf_align_size(void);
+size_t tlsf_block_size_min(void);
+size_t tlsf_block_size_max(void);
+size_t tlsf_pool_overhead(void);
+size_t tlsf_alloc_overhead(void);
+
+/* Debugging. */
+typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
+void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
+/* Returns nonzero if any internal consistency check fails. */
+int tlsf_check(tlsf_t tlsf);
+int tlsf_check_pool(pool_t pool);
+
+#if defined(__cplusplus)
+};
+#endif
+
+#endif

diff --git a/lib/microReticulum/src/main.cpp b/lib/microReticulum/src/main.cpp
new file mode 100755
index 0000000..04cb6c6
--- /dev/null
+++ b/lib/microReticulum/src/main.cpp
@@ -0,0 +1,23 @@
+// Only include if building locally and NOT testing
+#if defined(LIBRARY_TEST) && !defined(PIO_UNIT_TESTING)
+#include <stdio.h>
+
+#ifdef ARDUINO
+#include <Arduino.h>
+
+void setup() {
+ Serial.begin(115200);
+ Serial.print("\nSilly rabbit, microReticulum is a library!\n\nSee the examples directory for example programs that make use of this library.\n\n");
+}
+void loop() {
+}
+
+#else
+
+int main(void) {
+ printf("\nSilly rabbit, microReticulum is a library!\n\nSee the examples directory for example programs that make use of this library.\n\n");
+}
+
+#endif
+
+#endif

diff --git a/platformio.ini b/platformio.ini
index 90f8a59..ea8d598 100755
--- a/platformio.ini
+++ b/platformio.ini
@@ -61,7 +61,6 @@ lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.12.0
- attermann/microReticulum
[env:rnode-ng-21]
platform = espressif32
@@ -76,7 +75,6 @@ lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.12.0
- attermann/microReticulum
[env:ttgo-t-beam]
platform = espressif32
@@ -90,7 +88,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-t-beam-sx1262]
platform = espressif32
@@ -105,7 +102,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-t-beam-supreme]
platform = espressif32
@@ -122,7 +118,6 @@ lib_deps =
XPowersLib@^0.2.1
Adafruit_SH110X
adafruit/Adafruit SH110X@^2.1.14
- attermann/microReticulum
[env:lilygo-t3-s3]
platform = espressif32
@@ -137,7 +132,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:lilygo-t3-s3-sx127x]
platform = espressif32
@@ -152,7 +146,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:lilygo-t3-s3-sx1280-pa]
platform = espressif32
@@ -167,7 +160,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:lilygo-t-deck]
platform = espressif32
@@ -195,7 +187,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-lora32-v1]
platform = espressif32
@@ -209,7 +200,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-lora32-v2]
platform = espressif32
@@ -223,7 +213,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-lora32-v2-extled]
platform = espressif32
@@ -238,7 +227,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-lora32-v21]
platform = espressif32
@@ -252,7 +240,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-lora32-v21-extled]
platform = espressif32
@@ -267,7 +254,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:ttgo-lora32-v21-tcxo]
platform = espressif32
@@ -282,7 +268,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:heltec_wifi_lora_32_V2]
platform = espressif32
@@ -296,7 +281,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:heltec_wifi_lora_32_V2-extled]
platform = espressif32
@@ -311,7 +295,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:heltec_wifi_lora_32_V3]
platform = espressif32
@@ -325,7 +308,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:heltec_wifi_lora_32_V4]
platform = espressif32
@@ -340,7 +322,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:heltec_V4_boundary]
platform = espressif32
@@ -371,7 +352,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
monitor_filters = esp32_exception_decoder
[env:heltec_V4_boundary-local]
@@ -396,7 +376,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- microReticulum=symlink://../microReticulum
monitor_filters = esp32_exception_decoder
[env:featheresp32]
@@ -411,7 +390,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:seeed_xiao_esp32s3]
platform = espressif32
@@ -425,7 +403,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:generic-esp32]
platform = espressif32
@@ -439,7 +416,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- attermann/microReticulum
[env:wiscore_rak4631]
platform = nordicnrf52
@@ -457,7 +433,6 @@ build_flags =
-DRNS_USE_ALLOCATOR=1
lib_deps =
${env.lib_deps}
- attermann/microReticulum
@@ -477,7 +452,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- microReticulum=symlink://../microReticulum
Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash
monitor_filters = esp32_exception_decoder
@@ -494,7 +468,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- microReticulum=symlink://../microReticulum
monitor_filters = esp32_exception_decoder
[env:heltec_wifi_lora_32_V4-local]
@@ -510,7 +483,6 @@ build_flags =
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
- microReticulum=symlink://../microReticulum
monitor_filters = esp32_exception_decoder
[env:wiscore_rak4631-local]
@@ -532,7 +504,6 @@ build_flags =
build_unflags = -fno-exceptions
lib_deps =
${env.lib_deps}
- microReticulum=symlink://../microReticulum
Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash
[env:heltec_t114_local]
@@ -554,4 +525,3 @@ lib_deps =
${env.lib_deps}
https://github.com/liamcottle/esp8266-oled-ssd1306#e16cee124fe26490cb14880c679321ad8ac89c95
adafruit/Adafruit NeoPixel@^1.12.0
- microReticulum=symlink://../microReticulum

Served by rngit 1.5.0 - Generated in 0.14s